DEV Community

Discussion on: Explain Async/Await Like I'm Five

Collapse
 
qm3ster profile image
Mihail Malo

A Promise is a "handle" to a value that may or may not already be available.
It's better than a callback, because you can start the asynchronous process first, and think about what you will do with the outcome of it later.

do_thing(result => console.log(result))
// <- Here is already too late to change your mind. And you don't have access to `result` either.
const promise = do_thing()
if (logging) promise.then(result => console.log(result))
promise.then(result => {/* still available here, without running `do_thing` twice */})

but what is await? It allows you to wait until the value of a promise is available within synchronous-looking code, with all the usual imperative constructs.

const promise = do_thing()
if (logging) console.log(await promise)
something_else(await promise)

How is this achieved? When you execute "await", your function pauses, and allows other things to run.
Then, when the awaited result is ready, the function continues execution from that point onward, as if "await" was just a function that took a long time.
Think of it like the code before the await and the code after the await are two separate functions, just keeping the same variable scope.
That's why it can only be used in async functions: since the function doesn't "finish" (return the actual value and end forever) when called, but later, it also must return a Promise. The async annotation is just so that people are not confused why the function returns a Promise when called instead of what was actually passed to return statements in it.

Understanding that it's Promises underneath, and that each await stops the function and puts it in a queue tells us how we should optimize for good performance:

  1. For example, we shouldn't await the same value multiple times where we can easily avoid it:
const value = await do_thing() // fix for our example above
if (logging) console.log(value)
something_else(value)
  1. But we shouldn't immediately await every Promise we get our hands on, because there might be more work we can spawn before we need the result!
const urls = ['/a', '/b', '/c']
async bad_fetch(){
  const results = []
  for (const url of urls) {
    const res = await fetch(url)
    const data = await res.json()
    results.push(data.property)
  }
  return results
}
async good_fetch() {
  const results = []
  // now we start all requests, as well as parsing of their data, before "yeeting" from the function
  for (const data of await Promise.all(urls.map(url => fetch(url).then(x=>x.json()))) {
    results.push(data.property)
  }
  return results
}

The same, but without then and push (previous was written to be similar to the bad example)

const good_fetch = async () =>   Promise.all(
    urls.map( async url =>
      (await (await fetch(url)).json()).data
  )
Collapse
 
qm3ster profile image
Mihail Malo

Gotchas

All throws in a function marked async are rejections, never throws from the initial function call.

const throws_fast = async () => {console.log('running');throw 'fast'}
let p
try {p = throws_fast()} catch (err) {console.log('synchronous catch')}
console.log('first catch over')
try {await p} catch (err) {console.log('awaited catch')}

This outputs

running <- so the body DID actually run synchronously on call
first catch over <- then things after the call
awaited catch <- and only in await we get the error

But this isn't always true for all Promise-returning functions!

const throws_fast = async () => {console.log('running');throw 'fast'}
const throws_fast_really = (fast) => {if (fast) throw 'really fast'; return throws_fast()}
let p
try {p = throws_fast_really()} catch (err) {console.log('synchronous catch')}
console.log('first catch over')
try {await p} catch (err) {console.log('awaited catch')}
console.log('second attempt!')
let p2
try {p2 = throws_fast_really(true)} catch (err) {console.log('synchronous catch')}
console.log('first catch over')
try {await p2} catch (err) {console.log('awaited catch')}
running
first catch over
awaited catch
second attempt!
synchronous catch
first catch over