The other day, reviewing code, I encountered:
async function loadUrl(u) { var v; await fetch(u).then( (res)=>res.json() ).then( (js)=>{ v=js; } ); updateUI(v); }
I thought, “async-await
, excellent stuff” I didn’t quite understand async-await, so I read up on it. The more I read, the more I realised this code exhibits a misunderstanding of async-await.
The first misconception I had was that an async function would somehow hold up execution until the answer was ready. What does this code print?
async function theValue() { let r = await Promise.resolve(42); return r; } console.log(theValue());
I had assumed, since the return is after the await, is would somehow magically pause at await, and not return from the function until the Promise had resolved to 42. I was wrong.
It returns a Promise.
Promise { <state>: "pending" }
What about this code?
async function theValue() { let r = await Promise.resolve(42); console.log(r); return r; } console.log(theValue());
This code will output a Promise object, and then output 42.
Promise { <state>: "pending" } 42
The order of this output is really important. The 42 is coming from the console.log(r)
while the Promise comes from console.log(theValue())
.
async-await
is not a new feature of javascript. It’s just syntactic sugar, something that could be implemented in a macro-supporting language.
It works something like this:
async function X() { CODE1...; let ARG1 = await FN1(args); CODE2...; return RETVAL; }
becomes:
function X() { return new Promise( function(resolve) { CODE1...; resolve(FN1(args)); } ).then( function(ARG1) { CODE2...; return Promise.resolve(RETVAL); } ); }
There’s some syntactic sugar to share variables between the various .then
functions, but that’s essentially all that’s happening.
Which brings me back to the original code I was reviewing:
async function loadUrl(u) { var v; await fetch(u).then( res=>res.json() ).then( js=>{ v=js; } ); updateUI(v); }
The code smells because the author doesn’t really understand async-await, or promises. You can easily write this without async-await:
function loadUrl(u) { fetch(u).then( res=>res.json() ).then( js=>updateUI(js) ); }
Or you can write it using async-await:
async function loadUrl(u) { let res = await fetch(u); let js = await res.json(); updateUI(js); }
The mix of async and .then, though, makes me suspect the author just copied and pasted code from the web, without really understanding it. I think the async-await version is the easier to read, but the .then version is perfectly legible as well. The mixed version, though, just confuses.