Patterns for Asynchronous Programming With Promises

Promises are currently the best tool we have for asynchronous programming and they appear to be our best hope for the forseeable future, even if they’ll be hiding behind generators or async functions. For now, we’ll need to use promises directly, so we should learn some good techniques for using them right now, especially when dealing with asynchronous operations on collections, whether they happen in parallel or sequentially.

Before We Start

In the code, asyncOperation just represents a function that takes a single number parameter, performs an asynchronous operation according to that number, and returns a promise, while // ... represents whatever code is specific to your application that operates on the values returned from asyncOperation.

Each of the functions I create, it will run the asyncOperation on all of the values in the values array and return a promise that resolves to an array of the values that asyncOperation provides.

Parallel Asynchronous Operations

First we’ll take a look at parallel operations. This refers to getting multiple asynchronous operations queued up and running at the same time. By running them in parallel, you can significantly increase your performance. Sadly, this isn’t always possible. You may be required to run the operations in sequential order, which what we’ll be talking about in the next section.

Anyway, we’ll first look at running the asynchronous operations in parallel, but then performing synchronous operations on them in a specific order after all of the asynchronous operations have finished. This gives you a performance boost from the parallel operations, but then brings everything back together to do things in the right order when you need to.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function parallelAsyncSequentialSync () {
var values = [1,2,3,4];
// Use `map` to create an array of promises by performing
// `asyncOperation` on each element in the original array.
// They should happen in parallel.
var operations = values.map(asyncOperation);
// Return a promise so outside code can wait for this code.
return Promise.all(operations).then(function(newValues) {
// Once all of the operations are finished, we can loop
// through the results and do something with them
newValues.forEach(function(value) {
// ...
});
// Make sure we return the values we want outside to see
return newValues;
});
}

We use map to get all of our asynchronous operations fired off right away, but then use Promise.all to wait for them all to finish, and then we just run a loop over the new values and do whatever operations we need to do in the original order.

Sometimes, the order that our synchronous operations run in don’t matter. In this case, we can run each of our synchronous operations immediately after their respective asynchronous operations have finished.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function parallelAsyncUnorderedSync () {
var values = [1,2,3,4];
// Use `map` to create an array of promises
var operations = values.map(function(value) {
// return the promise so `operations` is an array of promises.
return asyncOperation(value).then(function(newValue) {
// ...
// we want the new values to pass to the outside
return newValue;
});
});
// return a promise so the outside can wait for all operations to finish.
return Promise.all(operations);
}

For this, we use map again, but instead of waiting for all of the operations to finish, we provide our own callback to map and do more inside of it. Inside we invoke our asynchronous function and then call then on it immediately to set up our synchronous operation to run immediately after the asynchronous one has finished.

Sequential Asynchronous Operations

Let’s take a look at some patterns for sequential asynchronous operations. In this case, the first asynchronous operation should finish before moving on to the next asynchronous operation. I have two solutions for doing this, one uses forEach and one uses reduce. They are quite similar, but the version with forEach needs to store a reference to the promise chain, whereas the version with reduce passes it through as the memo. Essentially, the version with forEach is just more explicit and verbose, but they both accomplish the same thing.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function sequentialAsyncWithEach () {
var values = [1,2,3,4];
var newValues = [];
var promise = Promise.resolve(null);
values.forEach(function(value) {
promise = promise.then(function() {
return asyncOperation(value);
}).then(function(newValue) {
// ...
newValues.push(newValue);
});
});
return promise.then(function() {
return newValues;
});
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function sequentialAsyncWithReduce () {
var values = [1,2,3,4];
var newValues = [];
return values.reduce(function(memo, value) {
return memo.then(function() {
return asyncOperation(value);
}).then(function(newValue) {
// ...
newValues.push(newValue);
});
}, Promise.resolve(null)).then(function() {
return newValues;
});
}

In each version we just chain each asynchronous operation off of the previous one. It’s annoying that we need to create a “blank” promise that is simply used to start the chain, but it’s a necessary evil. Also, we need to explicitly assign values to the newValues array (assuming you want to return those), which is another necessary evil, though maybe not quite as evil. I personally think the version with forEach is slightly easier to read thanks to its explicit nature, but it’s a stylistic choice and reduce works perfectly for this situation.

Conclusion

I used to think promises weren’t very straight-forward and even had a hard time finding a reason to use them over standard callbacks, but the more I need them, the more useful I find them to be, but I also find them to be more complicated with numerous ways they can be used, as shown above. Understanding your options and keeping a list of patterns you can follow greatly helps when the time comes to use them. If you don’t already have these patterns embedded in your brain, you may want to save them somewhere so you have them handy when you need them.

Well, that’s all for today. God bless! Happy Coding!

Author: Joe Zimmerman

Author: Joe Zimmerman Joe Zimmerman has been doing web development ever since he found an HTML book on his dad's shelf when he was 12. Since then, JavaScript has grown in popularity and he has become passionate about it. He also loves to teach others though his blog and other popular blogs. When he's not writing code, he's spending time with his wife and children and leading them in God's Word.