await

Pauses an async function until a promise settles and evaluates to its fulfilled value.

Signature
await expression
  • Added in JavaScript ECMAScript 2017 (ES8); widely available in browsers since March 2017. Top-level await added in ECMAScript 2022.

Parameters

ParameterTypeDescription
$expressionPromise | thenable | anyA Promise, a thenable object, or any value to wait for.

Return value

anyThe fulfillment value of the promise or thenable object, or, if the expression is not thenable, the expression's own value.

Errors

Throws the rejection reason if the awaited promise or thenable is rejected; a SyntaxError if used outside an async function body or module top level.

Changelog

  • PHP ES2022Top-level await allows await in the body of an ES module, outside any function. The importing module waits for the awaited value before it evaluates.
  • PHP ES2018for await...of added, giving await a loop form for async iterables.

await suspends the surrounding async function until the operand settles, then evaluates to the fulfilled value. If the operand rejects, await throws the rejection reason at that point in the code, so ordinary try/catch/finally works over asynchronous steps.

What it accepts

Any value. A native promise is awaited directly. A thenable — an object with a then method — is adopted by calling that method. A plain value is wrapped in an already-fulfilled promise, which is why await 20 is legal and gives back 20. That last case is not a no-op: even an already-settled operand defers the rest of the function by one microtask tick.

When to reach for it

  • Reading a result you need before the next statement can run.
  • Wrapping several asynchronous steps in a single try/catch.
  • Using return await rather than return promise inside try, so a rejection is caught locally and the frame appears in the stack trace.

What trips people up

await is not valid inside a callback passed to Array.prototype.forEach or map unless that callback is itself async — and an async callback in forEach returns a promise nobody waits for. Awaiting in sequence when the operations are independent is the other classic mistake; start them all, then await the collection.

Example

Try it yourself
Loading editor…
Press Run to execute the code.

When to use it

  • Read a value from an async source before the next statement uses it
  • Catch a rejection with try/catch instead of a .catch() callback
  • Sequence dependent steps: authenticate, then fetch, then write
  • Unwrap a thenable from a library that does not return native promises

Frequently asked questions

Why does await give me a SyntaxError?

Because await only works inside an async function body or at the top level of an ES module. In a classic script or a non-async function, wrap the code in an async IIFE: (async () => { await something(); })();

What happens if I await something that is not a promise?

You get the value straight back — await 20 evaluates to 20. It is not free, though: the rest of the function still resumes on the next microtask tick rather than continuing immediately.

How do I await inside a loop over an array?

A for...of loop supports await directly and runs the iterations one after another. If the iterations are independent, map the array to promises and await Promise.all(array.map(fn)) instead — forEach ignores the promises its callback returns.

More examples

await defers, even on a settled promise

The output order is 1, 2, 3, 4. The body of run() runs synchronously up to the first await, then control returns to the caller. Even though the awaited promise is already fulfilled, the code after it is queued as a microtask rather than running immediately.

Example · javascript
async function run() {
  console.log("1: body starts synchronously");
  await Promise.resolve(); // already fulfilled, still defers
  console.log("3: resumes on the next microtask");
}

run();
console.log("2: caller keeps going");

await null; // let the microtask queue drain
console.log("4: done");

Awaiting in sequence versus all at once

Both lines print two job names, but the sequential version takes roughly twice as long because the second job does not start until the first await resolves. Promise.all receives promises that are already running, so the total wait is the length of the slowest one.

Example · javascript
const job = (id, ms) =>
  new Promise((r) => setTimeout(() => r(`job${id}`), ms));

const start = Date.now();
const a = await job(1, 80);
const b = await job(2, 80);
console.log([a, b], `sequential ~${Date.now() - start}ms`);

const start2 = Date.now();
const both = await Promise.all([job(3, 80), job(4, 80)]);
console.log(both, `concurrent ~${Date.now() - start2}ms`);

Recovering from a rejection inline

Attaching .catch() before the await converts a rejection into a normal fulfilled value, so await never throws and no try block is needed. The first call logs the fallback and yields "cached data"; the second succeeds and yields "live data".

Example · javascript
const flaky = (ok) =>
  ok ? Promise.resolve("live data") : Promise.reject(new Error("offline"));

const value = await flaky(false).catch((err) => {
  console.log("falling back because:", err.message);
  return "cached data";
});

console.log("used:", value);
console.log("used:", await flaky(true).catch(() => "cached data"));

Discussion

  • Be the first to comment on this lesson.