Alternate working title: Awaiting for g’Doh!
TL;DR
- an
async
function is actually returning a promise behind the scenes
Details
See if you can figure out the output from this example:
import _Isofetch from 'isomorphic-fetch';
async function getIp_P() {
let ip = await fetch('https://api.ipify.org?format=json')
.then((response) => {
return response.json();
});
return ip;
}
function main() {
[1,2,3].map(async () => {
console.log('wan ip:');
console.log(await getIp_P());
});
}
main();
If you got it right you get a gold star for the day!
While await
within the async
function will “pause” execution, we must keep in mind that the async
keyword will convert the function to immediately return a promise. This means that after the first lambda function begins await
ing the second lambda function will immediately be called and so forth for each element of the mapped array.
The async
keyword signals the interpreter (transpiler in this case) to wrap the function in a promise and return the promise. So you can always await
an async function just like you can await
a promise - under the hood they are the same thing. This is why console.log(await getIp_P());
works - because getIp_P()
actually returns a promise.
Awaiting Callbacks
One way to solve this problem is to use a promise utility to wait until each callback is resolved, in this case bluebird’s Promise.map()
.
import _Isofetch from 'isomorphic-fetch';
import _Promise from 'bluebird';
async function getIp_P() {
let ip = await fetch('https://api.ipify.org?format=json')
.then((response) => {
return response.json();
});
return ip;
}
function main() {
_Promise.map([1,2,3], async () => {
console.log('wan ip:');
console.log(await getIp_P());
}, {concurrency: 1});
}
main();
The Final Test
So if you followed this post you should be able to correctly predict the output of the following code.
import _Isofetch from 'isomorphic-fetch';
async function getIp_P() {
let ip = await fetch('https://api.ipify.org?format=json')
.then((response) => {
return response.json();
});
return ip;
}
async function main() {
let i = 0;
while( i < 3 ) {
console.log('wan ip:');
console.log(await getIp_P());
i++;
}
}
main();
Because main()
is the only function running in the global context there will be no context switching while we’re waiting for getIp_P()
. So this version of the code operates sequentially as desired, in all its imperative ugliness.
note: punny alternate title stolen from this ;login: article: https://www.usenix.org/publications/login/dec15/beazley
update 2016-02-03 - slides from a talk based on this: https://chrishiestand.github.io/slides-2016-02-02-js-async-awaitier/
update 2016-01-25 - The first version of this article contained likely misinformation about callbacks and the event loop. It has been corrected and I apologize. I said that the argument to Array.prototype.map()
is a callback that gets put on the event loop; that was a misunderstanding and I no longer think it gets put on the event loop. The reason why I think but do not know it’s untrue is because it’s hard to verify what’s on the event loop and I haven’t read through to source code of v8 to be sure.
Despite not being a n00b, I made a n00b mistake which only became apparent when using async/await. I was using await
yet the code was behaving asynchronously. wtf, mate?
This post explains wtf, mate.
Comments
comments powered by Disqus