Careful Examination of JavaScript Await
Recently I found myself getting a bit confused writing some JavaScript code with async/await. I worked through in some detail what happens when we await
, and I thought it might be helpful to publish an article about it (for my future self as much as other readers!).
tl;dr:
await
is similar toyield
but there are some differences.
The following code will hopefully clarify what happens with async/await
in JavaScript. Can you figure out what it will do?
const asyncTask = () => {
console.log('asyncTask started')
const promise = new Promise(resolve => {
setTimeout(() => {
console.log('asyncTask resolving promise')
resolve('1000')
}, 2000)
})
console.log('asyncTask returning promise')
return promise
}
const asyncFunction = async () => {
console.log('asyncFunction started')
const promise = asyncTask()
const awaitResult = await promise
console.log('returning from asyncFunction, awaitResult = "'
+ awaitResult + '"')
return 'I am returning with "' + awaitResult + '"'
}
const timer = () => setInterval(()=>console.log('tick'), 500)
//start of main
const t = timer()
const mainPromise = asyncFunction()
console.log('mainPromise = ' + mainPromise)
mainPromise.then((result) => {
console.log('mainPromise has resolved, result = ' + result)
//stop timer
clearInterval(t)
})
console.log('end of main code')
Here is the output:
C:\dev>node promises.js
asyncFunction started
asyncTask started
asyncTask returning promise
mainPromise = [object Promise]
end of main code
tick
tick
tick
asyncTask resolving promise
returning from asyncFunction, awaitResult = "1000"
mainPromise has resolved, result = I am returning with "1000"
JavaScript does some tricky things behind the scenes with await
so I think it may be helpful to carefully go over this code in order to see what happens at each step:
- In the main code, we start a timer.
- Next, we call
asyncFunction
. - In
asyncFunction
, we callasyncTask
. asyncTask
creates a promise.- The promise initiates a
setTimeout
. asyncTask
returns the promise toasyncFunction
.- In
asyncFunction
, we nowawait
the promise returned fromasyncTask
. - This part is important:
await
is very similar toyield
in a generator function. What happens here is thatasyncFunction
is temporarily suspended and “returns” early back to the “main” code. IfasyncFunction
were a generator function, then we could resume it in our own code by calling itsnext
method. However, we will see that is not quite what happens in this case. - What is yielded when
asyncFunction
is suspended? It turns out that the JavaScript runtime creates a new promise at this point and that’s what is assigned to themainPromise
variable. It’s important to realize this promise is different from the one thatasyncTask
returns. - Now the rest of the “main” code runs and we see “end of main code” printed to the console. However, the JavaScript runtime doesn’t exit because it still has work to do! After all, we still have a
setTimeout
pending (as well as our timer’ssetInterval
) . - Once two seconds have gone by (we can see this happening via our timer’s “ticks”),
setTimeout
‘s callback function is invoked. - This callback function in turn resolves the promise that is currently being awaited by
asyncFunction
. - When the promise is resolved, the JavaScript runtime resumes
asyncFunction
from where it was suspended byawait
. This is very similar to callingnext
on a generator function, but here the runtime does it for us. - Since there are no more
await
statements,asyncFunction
now runs to completion and actually properly returns. - What happens when asyncFunction returns? After all, it was already suspended earlier, and at that point, it yielded a promise that was assigned to the
mainPromise
variable. - What happens is that the JavaScript engine intercepts the return and uses whatever value is in the return statement to fulfill the promise it created earlier.
- We can see that this happens, because now the callback supplied to
mainPromise.then
is actually executed. - We returned a string from
asyncFunction
that included the value of the resolved promise from asyncTask: Therefore that’s the string that is passed asresult
to the callback inmainPromise.then((result) => { console.log('mainPromise has resolved, result = ' + result) })
- We can see that this happens, because now the callback supplied to
Since this stuff can easily get confusing, let’s summarize:
await
in anasync
function is very similar toyield
in a generator function: In both cases the function is suspended and execution returns to the point from which it was called.- However,
await
is different in the following ways:- The JavaScript runtime will create a new promise and that’s what is yielded when the function is suspended.
- When the promise that is being
await
ed is fulfilled, the JavaScript runtime will automatically resume theasync
function - When the
async
function returns normally, the JavaScript runtime will use the function’s return value to fulfill the promise that the runtime created earlier.
References:
Aync function Await Generator function Iterators and generators
Related: