I read once that async and await were coming to ES2015 (ES6) and thought “hmm… that’s nice – call me when it works in ES5.” Time passed. Then Microsoft announced TypeScript 1.7 support for async/await – but only for ES2015 or above. More time passed. Then recently, Hell froze over and ES5 support arrived with TypeScript 2.1.
Prior to async/await, I used jQuery “Deferreds” to manage asynchronous function calls. My needs were simple – typically requiring one asynchronous function call. Sometimes, two – called sequentially or in parallel. In all cases, jQuery “Deferreds” met my needs.
But I could never remember the intricacies of these jQuery “Deferreds”. Anytime I needed to work with asynchronous functions, I had to review – once more – how Promises worked, how to chain together two function calls, how to handle errors.
I wondered, would async/await make asynchronous programming easier? The logic clearer? The error-handling better? I investigated. And here is what I found.
Yes, async/await does make asynchronous function call error-handling easier to understand, and easier to implement. And I found that async/await makes it easier to know which code fragments will “run now” versus those that will “run later”.
If you recall from my last blog post, I inquired if async/await reduced the number of lines of code in my jQuery “Deferred” code (and as mentioned above, the answer is “yes”). But there are additional benefits – better syntax, better error-handling – which make async/await a must going forward.
Let me show you two simple examples I used to explore the goodness of async/await. In the first example, I used a classic “Promises-only” approach (well… classic for me, anyway). The second example uses async/await.
Example 1 – Calling two asynchronous functions using Promises
Here is the first example (the VS Code project files are here). It consists of two asynchronous functions, asynch1 and asynch2, and a function that calls them sequentially and another function that calls them in parallel.
The getAsyncDataSequentially function calls asynch1 and waits for it to return before calling asynch2, and then waits for asynch2 to return before proceeding. This is the classic “sequential” pattern.
The getAsyncDataInParallel function calls asynch1 and then – without waiting – calls asynch2 – allowing both asynchronous functions to execute in parallel, and waits for both to return before proceeding. This is the classic “parallel” pattern.
Note that asynch1 and asynch2 are identical, except for their name, and the duration of their setTimeout delays. They simulate asynchronous ajax functions that take different amounts of time to complete.
I’m not going to delve into the mechanics of how all this works, since I expect you’ve already used these patterns yourself. But you may not be familiar with the import statement at the top
With a “Promises only” approach, you use .then to resolve a promise, and .catch to handle errors. These add considerable complexity to the getAsyncDataSequentially function, due to the need to nest them. The getAsyncDataInParallel function is less complex because we don’t need to nest .then and .catch.
Example 2 – Calling two asynchronous functions using async/await
Example 2 (the VS Code project files are here) refactors Example 1 to use async/await. I was expecting syntactic miracles with async/await… hoping they’d remove the need for Promises. But it was magical thinking on my part. Promises are still needed in the asynchronous functions. So asynch1 and asynch2 don’t get refactored. The refactoring is with getAsyncDataSequentially and getAsyncDataInParallel – the functions that call asynch1 and asynch2. This was a surprise.
Here is a side-by-side comparison of getAsyncDataSequentially – before and after using async/await… so you can see for yourself:
To my eyes… async/await (on the right) cleans up the code (on the left) beautifully. In the refactored code, error-handling is exclusively try/catch. And 10 lines of code were eliminated. These benefits – cleaner code, better error-handling – convinced me to use async/await going forward.
Here’s getAsyncDataInParallel – before and after:
executes before the .then block. But not the async/await code on the right. It will pause at the await statement, and proceed only after the asynchronous calls complete (with or without an error). I found this much more intuitive.
Feel free to try out the examples yourself (I’ve configured a launch.json file so you can execute & debug them from within VS Code).
In my last blog post, I promised to refactor my Client Side Rendering example to see if async/await improved the code. It did… consistent with the examples above. The VS Code files are here.
Here is a comparison of using jQuery Deferreds vs ES2015 Promises in the getRoles asynchronous function:
I replaced the jQuery “Deferred” statements with “real Promises”. Other than that, the code remained the same.
The bigger change was in the postProcessEditForm function, which calls two asynchronous functions using a parallel pattern:
Both of the postProcessEditForm functions above call two asynchronous functions – GetRoles and GetCTMemberValues – “in parallel”, pausing until both return. The jQuery “Deferred” version uses $.when to pause, the async/await version uses await.
I didn’t use error-handling in the jQuery Deferred version (no reason not to, I merely left it out of the example), but I used try/catch with the async/await version, and I find it easy to understand.
I heartily recommend using async/await and real “ES2015 Promises” in place of jQuery “Deferreds”. They make your code cleaner, and the error-handling better. And you will get the benefit of less code if you need to chain several asynchronous functions sequentially.
You’ll continue to need Promises… async/await requires them.
One thought on “Was waiting for async/await worth it?”