I decided to write this post as the result of attempting to find a pattern for executing any number of asynchronous tasks in a specified order with javascript. It came as a surprise to me that this isn’t supported out of the box in any modern browers javascript engine.
After googling around for a few minutes I came across this related blog post Decembersoft Inc. which comes close but doesn’t yield a working solution. I would reccomend checking it out as it was useful for me, and perhaps may hold some value for you as well. However, I will give you the gist of it here:
- Javascript allows you to chain promises in a serial fashion when the tasks are known beforehand and can be hard coded:
return task1.then(task2).then(task3);
-
This pattern does not allow you to programatically execute an unspecified number of asynchronous operations in a given order
-
Reduce to the rescue:
We’re going to use the Array.reduce() function to collapse the array of promises into a single promise chain.
// Serial
return [
task1,
task2,
task3,
].reduce((promiseChain, currentTask) => {
// Note: promiseChain === initialPromise
// on the first time through this function
/* TODO */
}, initialPromise);
(from the blog post)
- Which then leads to:
const reducer = (promiseChain, currentTask) => {
return promiseChain.then(chainResults =>
currentTask.then(currentResult =>
[ ...chainResults, currentResult ]
)
);
}
const tasks = getTaskArray();
tasks.reduce(reducer, Promise.resolve([]));
Okay cool, this is where you realize that it is their authors aim to collect the RESULTS of the promise chain in a sequential order, but not actually fire them off one after another. If you’re not sure or don’t believe me try this in your console:
function getTasks() {
const task1 = new Promise((resolve) => {
setTimeout(() => {
console.log('resolve task1');
resolve()
}, 2 * 1000);
});
const task2 = new Promise((resolve) => {
setTimeout(() => {
console.log('resolve task2');
resolve()
}, 2 * 1000);
});
const task3 = new Promise((resolve) => {
setTimeout(() => {
console.log('resolve task3');
resolve()
}, 2 * 1000);
});
return [
task1,
task2,
task3,
]
}
var reducer = (promiseChain, currentTask) => {
return promiseChain.then(currentTask);
}
var tasks = getTasks();
tasks.reduce(reducer, Promise.resolve());
All of these pending promises fire off at the same time as they’ve already been instantiated. Well shoot, back to square one. Just kidding, this pattern actually get’s us 90% of the way there. However, instead of having an array of already existing promises we want to use an array of functions that RETURN the promises we want to fire off. This looks like this:
function getTasks() {
const task1 = function() {
return new Promise((resolve) => {
setTimeout(() => {
console.log('resolve task1');
resolve()
}, 2 * 1000);
});
}
const task2 = function() {
return new Promise((resolve) => {
setTimeout(() => {
console.log('resolve task2');
resolve()
}, 2 * 1000);
});
}
const task3 = function() {
return new Promise((resolve) => {
setTimeout(() => {
console.log('resolve task3');
resolve()
}, 2 * 1000);
});
}
return [
task1,
task2,
task3,
]
}
var reducer = (promiseChain, currentTask) => {
return promiseChain.then(currentTask);
}
var tasks = getTasks();
tasks.reduce(reducer, Promise.resolve());
There, now you can can execute any arbitrary number of promises one after another. All you need to do in your code is collect an array of references to the functions that create/instantiate/return the promise that is the asynchronous operation you want to perform.
EDIT: This pattern is not as unique as I initially thought. There are various implementations that already exist out there on the web, heres another one: