How to Execute Javascript Promises in Sequence

Published by on

From time to time, I am faced with the problem of having to execute several promises in a fixed order in JavaScript.

Unlike promises that are executed in parallel (via Promise.all()), to my knowledge there is no method for this on the Promise object. So you have to build this functionality yourself.

This is how I solved this problem for myself:

// An example of long running funtion returning a promise.
// for demonstration purposes we just wrap a promise around setTimeout.
function longRunningFunctionReturningAPromise(parameter) {
	const taskDuration = Math.floor(Math.random() * 110);

	return new Promise((resolve, reject) => {
		const calculationResult = Math.sin(parameter * parameter) * 2;

		if (parameter > 3) {
			// Example for an error.
			reject(new Error(`Parameters greater than 3 are not allowed.`));
		} else {
			setTimeout(() => {
				resolve(calculationResult);
			}, taskDuration);
		}
	});
}

// Create a promise sequence from an array of tasks.
function createSequence(tasks) {
	let sequenceResults = [];

	// The initial sequence Promise object:
	// Promise.resolve() returns a Promise. It also is "Thenable" so we
	// can append promises at the end of it by using sequence.then(...)
	let sequence = Promise.resolve();

	// Append all tasks to the promise
	// and execute them only after the previous task has finished.
	tasks.forEach((task, taskIndex) => {
		sequence = sequence
			.then(() => task())
			.then(taskResult => {
				// add the result of the task to the results array
				sequenceResults[taskIndex] = taskResult;
			});
	});

	// At the end of the sequence, return the sequence resutlts.
	return sequence.then(() => sequenceResults);
}

// Create a list of tasks. Note: we wrap each task in a separate anonymous function.
// This is important so we can invoke the tasks in order at a later point in time
// (instead of them being invoked immediately on creation)
const tasks = [
	() => longRunningFunctionReturningAPromise(1),
	() => longRunningFunctionReturningAPromise(2),
	() => longRunningFunctionReturningAPromise(3),
];

// Start a sequence that completes
createSequence(tasks).then(
	sequenceResults => console.log('Sequence completed:', sequenceResults),
	error => console.log('Sequence failed:', error)
);

const tasksThatWillFail = [
	() => longRunningFunctionReturningAPromise(2),
	() => longRunningFunctionReturningAPromise(3),
	() => longRunningFunctionReturningAPromise(4),
];

// Start a sequence that will throw an error
createSequence(tasksThatWillFail).then(
	sequenceResults => console.log('Sequence completed:', sequenceResults),
	error => console.log('Sequence failed:', error)
);

The following was important to me in this solution:

  • All Promises are wrapped in an anonymous function, so that they are not executed until the previous Promise has been resolved.
  • As soon as an error occurs and a Promise cannot be resolved, the sequence is stopped, no further Promises are executed and the errors that occurred are propagated.

Leave a comment

Available formatting commands

Use Markdown commands or their HTML equivalents to add simple formatting to your comment:

Text markup
*italic*, **bold**, ~~strikethrough~~, `code` and <mark>marked text</mark>.
Lists
- Unordered item 1
- Unordered list item 2
1. Ordered list item 1
2. Ordered list item 2
Quotations
> Quoted text
Code blocks
```
// A simple code block
```
```php
// Some PHP code
phpinfo();
```
Links
[Link text](https://example.com)
Full URLs are automatically converted into links.

Replied on your own website? Send a Webmention!