Is it possible to chain setTimout
functions to ensure they run after one another?
5 Answers
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).

- 683,504
- 96
- 985
- 979
-
I apologise for my dumb questions, but are there any internet browsers which process javascript functions concurrently, (including mobile), and if so does this ensure that they don't run concurrently? – xiatica Aug 04 '11 at 01:53
-
All browser javascript is single threaded. One thread of execution runs at a time so sequential statements are always run one after the other. Two pieces of javascript NEVER run concurrently. – jfriend00 Aug 04 '11 at 02:02
-
Added a chainable timer object with queuing and a cancel method. – jfriend00 Feb 11 '16 at 22:46
-
Can the fn argument of delay be a function that takes an argument like function logMessage(message) { log(message); }? And if so, how could I pass in the message argument? – Eugene Brown Jun 09 '16 at 06:26
-
@GeeBrownit - You can't pass `logMessage(message)` directly. You can make a wrapper function that calls `log(message)` itself. Or you can use `.bind()` to bind an argument to the callback you pass. I'd suggest you ask your own question about how to do that. – jfriend00 Jun 09 '16 at 06:50
-
2Thanks, bind is sweet and works. For anyone whose interested here https://jsfiddle.net/efbbrown/ypa25gsc/1/ is how you can bind inputs to the fn argument. – Eugene Brown Jun 09 '16 at 08:30
-
Added a version using promises to handle the async sequencing for us. – jfriend00 Oct 04 '16 at 23:23
-
Why do you have `Promise.delay` and `Promise.prototype.delay`? Can you combine the 2? – guest Apr 26 '19 at 02:35
-
@guest - There are situations where you want a static function like `Promise.delay()` that returns a new promise and there are reasons why you want to insert a `.delay()` into an existing promise chain. These are separate use cases that require a slightly different wrapper around the core functionality. You can see from the code that `Promise.prototype.delay()` uses `Promise.delay()` in its implementation, but works on an existing promise chain. – jfriend00 Apr 26 '19 at 02:49
-
Added better summary of the three options listed here. – jfriend00 Nov 22 '19 at 19:42
Inspired by the Promise-based solution in jfriend00's answer, I demonstrated a shorter version:
Promise.resolve()
.then(() => delay(400))
.then(() => log1())
.then(() => delay(500))
.then(() => log2())
.then(() => delay(800))
.then(() => log3());
function delay(duration) {
return new Promise((resolve) => {
setTimeout(resolve, duration);
});
}
function log1() {
console.log("Message 1");
}
function log2() {
console.log("Message 2");
}
function log3() {
console.log("Message 3");
}

- 1,061
- 2
- 9
- 28

- 15,447
- 5
- 79
- 98
With ES6, this is pretty simple using async
/await
. This is also very easy to read and a little upgrade to the promises answer.
// Expect to be async
async function timePush(...arr){
function delay(t){
return new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve();
},t)
})
}
// for the length of this array run a delay
// then log, you could always use a callback here
for(let i of arr){
//pass the items delay to delay function
await delay(i.time);
console.log(i.text)
}
}
timePush(
{time:1000,text:'hey'},
{time:5000,text:'you'},
{time:1000,text:'guys'}
);

- 5,538
- 2
- 10
- 34

- 313
- 3
- 4
I have encountered the same issue. My solution was to call self by setTimeout
, it works.
let a = [[20,1000],[25,5000],[30,2000],[35,4000]];
function test(){
let b = a.shift();
console.log(b[0]);
if(a.length == 0) return;
setTimeout(test,b[1]);
}
the second element in array a is time to be delayed

- 30,378
- 5
- 37
- 62

- 11
- 1
Using async / await
with @Penny Liu example:
(async() => {
await delay(400)
log1()
await delay(500)
log2()
await delay(800)
log3()
})()
async function delay(duration) {
return new Promise((resolve) => {
setTimeout(resolve, duration);
});
}
function log1() {
console.log("Message 1");
}
function log2() {
console.log("Message 2");
}
function log3() {
console.log("Message 3");
}
-
I like this style of `delay` call, but `async` like a virus: one async, all (call stack functions) need be async – yurenchen Jun 18 '23 at 04:56