No, async/await won’t save you from Promises

One of the most remarkable announcements of the previous year was the adoption of ES7 features by NodeJS. The release of NodeJS version 7.6.0 was followed by tremendous applause by the JavaScript community that fueled a development frenzy. JavaScript developers were convinced that not only was the callback hell gone for good, but also so were promises. And it wasn’t just backend developers that were impressed. Frontend developers working on the hottest JS framework of the week were also impressed and refactoring all of their services and asynchronous calls to go with the hype. You could suddenly hear devs whispering during meetups about this amazing new feature of async/await like if it is the holy grail of innovation (hint, C# has been supporting this feature for years).

The reason why JavaScript developers loved async/await was primarily because of the given ability to write streamlined code without having to worry about callbacks and different orders of execution. It was like ES7 lifted the responsibility of writing optimized asynchronous code off of their shoulders. Everything seems simpler if you don’t have to use callbacks and yes the function you pass to .then() is still a callback. It’s pretty obvious to every sane person out there that going from this:

To this:

Is an improvement. And certainly this:

is probably as good as it can get.

So why still bother using promises? If you were tutoring a new JavaScript developer you might as well tell them to discard promises and learn to use async/await everywhere, right? WRONG!

Promises are still useful and let’s not forget that async/await does make use of promises under the hood. Did you know that you can attach a then call at the end of an async function you will be able to run a callback after the async function terminates? Also, did you know that if you omit the ‘await’ keyword you get back a promise? So yeah, promises are here to stay and there’s one particular use case where promises can still shine.

Suppose you have a few arbitrary sensors returning some temperatures, min, max, average, that short of thing. Let’s simulate the asynchronous nature of a system like that by using setTimeout:

So, now you have 3 functions taking a few seconds to return the sensor data. The first one takes 2000ms, the second one takes 1000ms and the third one takes 3000ms.

What if we needed to have a function that takes all sensor data and performs a computation based on a formula. We would have to wait for all 3 sensors to finish. Since all 3 functions use promises and we already know the connection between promises and async/await let’s do this using the latest ES7 feature:

Using the performance interface I measured the execution time of the above function to ~6000ms (6006ms to be precise). I don’t know about you but that seems like a lot of time to me and it definitely does not feel like it’s asynchronous at all. Sure we can do better.

The following code makes use of the invaluable Promise.all function:

Promise.all takes an array of promises and returns a single promise that gets resolved when all other promises get resolved. If we use the method one more time we can see that the execution time goes down to 3001ms. To compare the two functions you can use this fiddle (only run the fiddle in browsers that do support async/await):

If we change the third timeout’s delay from 3000ms to 4000ms and run the example the above code we see that it takes ~7000ms with async/await and ~4000ms with Promise.all, both reasonable values considering that we increased the delay by 1000ms.

It’s pretty evident that Promise.all is a better method for waiting for multiple promises to resolve but why? Well the reason stands in the order in which the async/await function calls the 3 sensor functions.

This is how the functions get called:

The getSensorAData function is called at the beginning and the getSensorBData function is not called until after getSensorAData has returned. And the same happens with getSensorCData. So we end up with a total time that is the sum of the functions’ execution times plus a few milliseconds because of the call to console.log and CPU time.

In the Promise.all approach however, all 3 functions are executed almost at the same time (not exactly the same time, remember JavaScript is single-threaded). So we end up with the following timeline:

That’s why this approach’s execution time almost matches the execution time of the function that takes the longest time to complete. A little side-note here, what you see in the image above is not multi-threading. JavaScript does not support that kind of thing. The image depicts the time it takes for each function to finish relative to its starting time. That does not mean that getSensorA, for example, occupies the browser thread for 2 seconds straight.

The improvement in execution time we achieved by using Promise.all is no small feat. In larger applications the improvement could be massive and until we are able to await multiple async functions at the same time and that feature is supported by the latest version of NodeJS and at least one transpiler, the Promise.all will be the perfect solution and the promise-based syntax will still be alive.