I have a variable can_run
, that can be either 1 or 0, and then I have a queue of functions, that should be run as soon as the variable is switched from 0
to 1
(but only 1 such function at a time).
Right now, what I do is
var can_run=1;
function wait_until_can_run(callback) {
if (can_run==1) {
callback();
} else {
window.setTimeout(function(){wait_until_can_run(callback)},100);
}
}
//...somewhere else...
wait_until_can_run( function(){
can_run=0;
//start running something
});
//..somewhere else, as a reaction to the task finishing..
can_run=1;
It works, however, it doesn't strike me as very efficient to have about 100 timeouts continuously running. Something like semaphore would be handy in here; but in general, semaphores are not really needed in JavaScript.
So, what to use here?
edit: I have written "queue of functions" but as seen here, I don't really care about the order.
Here is a nice Queue class you can use without the use of timeouts:
var Queue = (function () {
Queue.prototype.autorun = true;
Queue.prototype.running = false;
Queue.prototype.queue = [];
function Queue(autorun) {
if (typeof autorun !== "undefined") {
this.autorun = autorun;
}
this.queue = []; //initialize the queue
};
Queue.prototype.add = function (callback) {
var _this = this;
//add callback to the queue
this.queue.push(function () {
var finished = callback();
if (typeof finished === "undefined" || finished) {
// if callback returns `false`, then you have to
// call `next` somewhere in the callback
_this.dequeue();
}
});
if (this.autorun && !this.running) {
// if nothing is running, then start the engines!
this.dequeue();
}
return this; // for chaining fun!
};
Queue.prototype.dequeue = function () {
this.running = false;
//get the first element off the queue
var shift = this.queue.shift();
if (shift) {
this.running = true;
shift();
}
return shift;
};
Queue.prototype.next = Queue.prototype.dequeue;
return Queue;
})();
It can be used like so:
// passing false into the constructor makes it so
// the queue does not start till we tell it to
var q = new Queue(false).add(function () {
//start running something
}).add(function () {
//start running something 2
}).add(function () {
//start running something 3
});
setTimeout(function () {
// start the queue
q.next();
}, 2000);
Fiddle Demo: http://jsfiddle.net/maniator/dUVGX/
Updated to use es6 and new es6 Promises:
class Queue {
constructor(autorun = true, queue = []) {
this.running = false;
this.autorun = autorun;
this.queue = queue;
}
add(cb) {
this.queue.push((value) => {
const finished = new Promise((resolve, reject) => {
const callbackResponse = cb(value);
if (callbackResponse !== false) {
resolve(callbackResponse);
} else {
reject(callbackResponse);
}
});
finished.then(this.dequeue.bind(this), (() => {}));
});
if (this.autorun && !this.running) {
this.dequeue();
}
return this;
}
dequeue(value) {
this.running = this.queue.shift();
if (this.running) {
this.running(value);
}
return this.running;
}
get next() {
return this.dequeue;
}
}
It can be used in the same way:
const q = new Queue(false).add(() => {
console.log('this is a test');
return {'banana': 42};
}).add((obj) => {
console.log('test 2', obj);
return obj.banana;
}).add((number) => {
console.log('THIS IS A NUMBER', number)
});
// start the sequence
setTimeout(() => q.next(), 2000);
Although now this time if the values passed are a promise etc or a value, it gets passed to the next function automatically.
Fiddle: http://jsfiddle.net/maniator/toefqpsc/
I'm not sure the best way to do this in plain JS but many libraries have Deferred implementations which are very useful for this use case.
With jQuery:
var dfd = $.Deferred();
var callback = function() {
// do stuff
};
dfd.done(callback); // when the deferred is resolved, invoke the callback, you can chain many callbacks here if needed
dfd.resolve(); // this will invoke your callback when you're ready
EDIT One of the nice things about these library supported deferreds are that they are normally compatible with Ajax events, and in turn other Deferred objects so you can create complex chains, trigger events on Ajax complete, or trigger the 'done' callback after multiple conditions are met. This is of course more advanced functionality, but it's nice to have in your back pocket.
In addition to the other useful answers here, if you don't need the extras that those solutions provide, then an asynchronous semaphore is straightforward to implement.
It's a lower-level concept than the other options presented here though, so you may find those to be more convenient for your needs. Still, I think asynchronous semaphores are worth knowing about, even if you use higher-level abstractions in practice.
It looks something like this:
var sem = function(f){
var busy = 0;
return function(amount){
busy += amount;
if(busy === 0){
f();
}
};
};
And you invoke it like this:
var busy = sem(run_me_asap);
busy
is a function that maintains an internal counter of
asynchronous actions that it is waiting on. When that
internal counter reaches zero, it fires the function
run_me_asap
, which you supply.
You can increment the internal counter prior to running an
asynchronous action with busy(1)
and, then the
asynchronous actions is responsible for decrementing the
counter with busy(-1)
once it is complete. This is how we
can avoid the need for timers. (If you prefer, you could
write sem
so that it returns an object with inc
and
dec
methods instead, like in the Wikipedia article; this
is just how I do it.)
And that's all you have to do to create an asynchronous semaphore.
Here's an example of it in use. You might define the function
run_me_asap
as follows.
var funcs = [func1, func2, func3 /*, ...*/];
var run_me_asap = function(){
funcs.forEach(function(func){
func();
});
});
funcs
might be the list of functions that you wanted to
run in your question. (Maybe this isn't quite what you want,
but see my 'N.B.' below.)
Then elsewhere:
var wait_until_ive_finished = function(){
busy(1);
do_something_asynchronously_then_run_callback(function(){
/* ... */
busy(-1);
});
busy(1);
do_something_else_asynchronously(function(){
/* ... */
busy(-1);
});
};
When both asynchronous operations have completed, busy
's
counter will be set to zero, and run_me_asap
will be
invoked.
N.B. How you might use asynchronous semaphores depends on the architecture of your code and your own requirements; what I've set out may not be exactly what you want. I'm just trying to show you how they work; the rest is up to you!
And, one word of advice: if you were to use asynchronous
semaphores then I'd recommend that you hide their creation
and the calls to busy
behind higher-level abstractions so
that you're not littering your application code with
low-level details.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With