Was waiting for async/await worth it?

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”.

But async/await doesn’t remove your need to know what promises are, and how to use them. And I did not experience a significant reduction in lines of code after refactoring some legacy code to use async/await. And you can abandon all hope of trying to understand the ES5 code generated from the async/await statements, unless you’re a masochist (or work on the TypeScript team). The Typescript transpiler literally vomits bizarre ES5 code… bearing no semblance to the original TypeScript source. So you absolutely need to use Source Maps for debugging, and trust that Microsoft got the transpilation right. But I trust they did and I like debugging with Source Maps (as long as the output isn’t minified) so that I can follow the TypeScript code I wrote and not the JavaScript code emitted.

(Incidentally, you will benefit greatly if you understand how JavaScript uses a single threaded event loop in conjunction with a message queue to process “run now” and “run later” code fragments. There are many good articles explaining the details… just search on ‘JavaScript single threaded event loop’).

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.

fig01

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

import "es6-promise"
This is needed to bring in the ES5 “Promise” polyfill (and described nicely here).

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:

fig02.png

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:

fig03

The refactoring didn’t reduce the code… but I still like the improved try/catch error-handling. I also like the cleaner semantics.  In the example on the left, the statement on line 84:
console.log("3") ;

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:

fig04

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:

fig05

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.

In Conclusion…

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.

In my next blog post, I’ll see if the latest PnP JS Core JavaScript library  can simplify my code more than async/await did. From what I heard, it has functions for calling the SharePoint REST API without the need for Promises or async/await.

Stay tuned.

One thought on “Was waiting for async/await worth it?

  1. Pingback: Want Simplicity? Use the SharePoint PnP JavaScript Core Library | SharePoint for the Big Company

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s