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 | function parallelAsyncSequentialSync () { |
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 | function parallelAsyncUnorderedSync () { |
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 | function sequentialAsyncWithEach () { |
1 | function sequentialAsyncWithReduce () { |
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!