We’ve all been through callback hell, maybe we use Promises and Observables to get some relief. Will async await liberate us once and for all?
What happens in this piece of code, is that we pass a callback (the
onClick function) to the
onClick function as soon as possible.
Some of you might be familiar with callback hell. It’s basically when you have a lot of callbacks, and those callbacks register other callbacks, and other callbacks, and everything becomes one big spaghetti of callbacks.
A solution for making that situation better is Promises. You can think of Promises as convenient callback ‘containers’. They provide a cleaner interface for using callbacks:
As you can see, whe don’t directly pass a callback to the
fetch function, instead it returns an object with a
.then() method, to which you can pass the callback. The advantage is that the then method returns another promise, resulting in the ability to chain then’s. This is especially useful when doing multiple asynchronous operations. But be aware, these are still callbacks! Just more nice and tidy.
Promises are just a ‘nicer’, standardized way to deal with callbacks.
So even though callback hell is less likely with Promises, you can still get some nasty callback constructions, even with Promises:
Why do we need to nest the second Promise? Well, in this example we kinda need to, because we need the users after fetching the scores. We could probably solve this case differently, but you’re gonna get in these more complex situations at some point.
Async await is a new syntax that is released with ES2017. It uses two keywords: async and await to make asynchronous logic easier to write.
The async keyword can be used to mark a function as asynchronous:
Asynchronous functions always return a Promise. This
fetchUsersWithScores function will now return a Promise, even if it’s only doing synchronous logic.
The await keyword is then used to handle Promises inside the function:
We fetch the users using the same function as in the Promise example. But do you notice how we are not chaining
.then() to fetchUsers, although it returns a Promise? This is because await handles that Promise for us. It ‘pauses’ the function until fetchUsers is done, and returns the result.
Async marks a function as asynchronous, the function will always return a Promise. Await handles Promises inside the async function, making the function’s inner logic synchronous.
We can now fetch the scores and tie them together easily:
So long, callback hell!
Short answer, no! The async keyword marks a specific function as asynchronous, await only blocks the execution of that function, not all other functions in the application. You can still fully leverage have concurrency when using async await.
Note that the
fetchUsersWithScores function itself still returns a Promise:
We could actually run that function in parallel with something else if we wanted to:
And we could even create another async function that waits for both to be finished using
Promise.all()waits for all Promises in an array to succeed and returns their results as an array.
You get the drill? Make a function async, await Promises inside, and return the result. But what about errors?
Normally you can chain a
.catch() to a Promise to handle possible errors. However, as you’ve just seen, with await you get a single value as output. When an error occurs, it will throw the error and you can simply handle that with a regular try - catch:
Another thing you could do, is not catching the error inside the async function (because you might not be able to do anything useful with the error there), but chain a catch to the output of the async function:
How do we go about looping? You might be tempted to use the
Nope, won’t work! If you look closely, the await is inside a callback, which is another function, this will crash because that callback is not async. How about making it async?
This does work, but
saveUsers will not wait for the result of the
saveUser calls to be finished. We could try to use a map:
Hmmm, functionally this would work, but there is no point for that async callback to exist now. We could just map the
saveUser calls as promises and be done with it:
As you can see, no need for async await here.
There is also another point of attention, the
saveUser calls are now parallel. But what if you don’t want that? What if your database can only handle one at a time? This is a case where async await can come in handy again! A nice and clean wait to do this is by just using a simple for…of loop:
When doing repetitive asynchronous operations in parralel, map the promises to an array and await the
Promise.all(). When a sequential flow is required, use a
This is a great advantage of async await, you can use normal control structures like
if/else with asynchronous operations! Another example:
I must say I’m pretty exited for the future. Async await solves the issues with combining multiple Promises and makes complex asynchronous control flows more easy to code.
On the front-end you will need a transpiler like Babel to make async await shine. For Babel I would recommend using the ‘env’ preset with the ‘regenerator runtime’ to make it work. Note that there is a cost in the form of extra kilobytes when using the regenerator runtime.
- Philip Roberts: What the heck is the event loop anyway? | JSConf EU 2014 youtube.com/watch?v=8aGhZQkoFbQ