Is it possible to chain setTimout
functions to ensure they run after one another?
Advertisement
Answer
Three separate approaches listed here:
- Manually nest
setTimeout()
callbacks. - Use a chainable timer object.
- Wrap
setTimeout()
in a promise and chain promises.
Manually Nest setTimeout callbacks
Of course. When the first one fires, just set the next one.
setTimeout(function() { // do something setTimeout(function() { // do second thing }, 1000); }, 1000);
Chainable Timer Object
You can also make yourself a little utility object that will let you literally chain things which would let you chain calls like this:
delay(fn1, 400).delay(fn2, 500).delay(fn3, 800);
function delay(fn, t) { // private instance variables var queue = [], self, timer; function schedule(fn, t) { timer = setTimeout(function() { timer = null; fn(); if (queue.length) { var item = queue.shift(); schedule(item.fn, item.t); } }, t); } self = { delay: function(fn, t) { // if already queuing things or running a timer, // then just add to the queue if (queue.length || timer) { queue.push({fn: fn, t: t}); } else { // no queue or timer yet, so schedule the timer schedule(fn, t); } return self; }, cancel: function() { clearTimeout(timer); queue = []; return self; } }; return self.delay(fn, t); } function log(args) { var str = ""; for (var i = 0; i < arguments.length; i++) { if (typeof arguments[i] === "object") { str += JSON.stringify(arguments[i]); } else { str += arguments[i]; } } var div = document.createElement("div"); div.innerHTML = str; var target = log.id ? document.getElementById(log.id) : document.body; target.appendChild(div); } function log1() { log("Message 1"); } function log2() { log("Message 2"); } function log3() { log("Message 3"); } var d = delay(log1, 500) .delay(log2, 700) .delay(log3, 600)
Wrap setTimeout in a Promise and Chain Promises
Or, since it’s now the age of promises in ES6+, here’s similar code using promises where we let the promise infrastructure do the queuing and sequencing for us. You can end up with a usage like this:
Promise.delay(fn1, 500).delay(fn2, 700).delay(fn3, 600);
Here’s the code behind that:
// utility function for returning a promise that resolves after a delay function delay(t) { return new Promise(function (resolve) { setTimeout(resolve, t); }); } Promise.delay = function (fn, t) { // fn is an optional argument if (!t) { t = fn; fn = function () {}; } return delay(t).then(fn); } Promise.prototype.delay = function (fn, t) { // return chained promise return this.then(function () { return Promise.delay(fn, t); }); } function log(args) { var str = ""; for (var i = 0; i < arguments.length; i++) { if (typeof arguments[i] === "object") { str += JSON.stringify(arguments[i]); } else { str += arguments[i]; } } var div = document.createElement("div"); div.innerHTML = str; var target = log.id ? document.getElementById(log.id) : document.body; target.appendChild(div); } function log1() { log("Message 1"); } function log2() { log("Message 2"); } function log3() { log("Message 3"); } Promise.delay(log1, 500).delay(log2, 700).delay(log3, 600);
The functions you supply to this version can either by synchonrous or asynchronous (returning a promise).