Asynchronous JavaScript with async/await: concurrency, timers, and cancellation.

Introduction

async/await is syntax on top of promises for writing asynchronous code that reads like synchronous code.

async and await

async function load() {
  const res = await fetch('/data.json')
  return res.json()
}

load()                     // → Promise
try {
  const data = await load()
} catch (err) {
  console.error(err)       // rejection lands here
}

An async function always returns a promise; await unwraps it. return resolves, a throw rejects, and top-level await works in ES modules only.

See: async function, await (developer.mozilla.org)

Running in parallel

// Sequential: one after another
for (const id of ids) {
  results.push(await getItem(id))
}
// Parallel: start all, then await all
const results = await Promise.all(ids.map(getItem))
CallResolves when
Promise.all()all fulfill, else rejects
Promise.allSettled()every one settles
Promise.any()first one fulfills
Promise.race()first one settles

Awaiting in a loop is sequential; map plus Promise.all starts all at once. Promise.all() fails fast; Promise.any() throws AggregateError if every input rejects.

See: Promise.all(), Promise.allSettled(), Promise.any() (developer.mozilla.org)

Timers

setTimeout()

const id = setTimeout(() => {
  console.log('later')
}, 1000)

clearTimeout(id)

setInterval()

const id = setInterval(() => {
  console.log('tick')
}, 1000)

clearInterval(id)

sleep()

const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))

await sleep(1000)

setTimeout() runs once; setInterval() repeats until cleared. sleep() wraps a timer in a promise so it can be awaited.

See: setTimeout(), setInterval(), clearTimeout() (developer.mozilla.org)

Cancellation

const controller = new AbortController()

fetch('/data.json', { signal: controller.signal })
  .catch(err => console.error(err.name))

controller.abort()
// Built-in timeout signal
const signal = AbortSignal.timeout(5000)

await fetch('/data.json', { signal })

Pass signal to fetch() and call abort() to cancel a request. A manual abort rejects with AbortError; a timeout rejects with TimeoutError.

See: AbortController, AbortSignal.timeout() (developer.mozilla.org)

Pitfalls

load()                     // floating promise, unhandled
items.forEach(async item => {
  await save(item)         // forEach ignores this
})
for (const item of items) {
  await save(item)         // sequential, one at a time
}

Missing await leaves a floating promise, so rejections go unhandled; forEach() ignores its async callback; awaiting in a loop is sequential (fine for rate limits).

See: async function, await (developer.mozilla.org)

0 Comments for this cheatsheet. Write yours!