async function

Declares a function that always returns a promise and may pause on await inside its body.

Signature
async function name(param0, param1, /* …, */ paramN) {
  statements
}
  • Added in JavaScript ECMAScript 2017 (ES8); widely available in browsers since April 2017

Parameters

ParameterTypeDescription
$nameidentifierThe function's name.
$param0, param1, …, paramNoptionalanyThe names of the formal parameters for the function; the same parameter syntax as an ordinary function, including defaults, rest and destructuring.
$statementsoptionalcode blockThe statements comprising the body of the function. The await mechanism may be used inside it.

Return value

PromiseAn AsyncFunction object. Each call returns a new Promise that is resolved with the value returned by the function body, or rejected with an exception uncaught inside it.

Errors

Never throws synchronously to the caller: an exception thrown in the body (including a bad argument) settles the returned promise as rejected instead.

Changelog

  • PHP ES2018Async generator functions (async function*) and for await...of were added, so an async function body can also yield asynchronously.
  • PHP ES2022Top-level await landed in modules, so module-level code no longer needs an async wrapper function to await.

Marking a function async does two things: it lets you use await in the body, and it changes the return type. Whatever the body returns is wrapped in a promise, and whatever it throws becomes a rejection. The function itself still starts running synchronously — it only suspends at the first await, at which point control returns to the caller.

When to reach for it

  • Any time a function's result depends on something that settles later and you want straight-line code instead of .then() chains.
  • To normalise a mixed API: an async function that sometimes returns a cached value and sometimes fetches one still hands back a promise in both cases.
  • To get try/catch/finally working over asynchronous steps.

What trips people up

The most common mistake is calling an async function and using the result as if it were the value: const n = double(21) gives you a promise, not a number. The second is firing several async calls in a loop with await in each iteration, which serialises work that could have run together — collect the promises and pass them to Promise.all() instead.

Errors also change shape. An async function never throws to its caller synchronously, so a try/catch wrapped around the call site catches nothing unless you await the call inside it.

Example

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

When to use it

  • Load and parse a config file, returning the parsed object as a promise
  • Retry a flaky operation in a loop with try/catch and a delay between attempts
  • Wrap a callback-style API once so the rest of the codebase can await it
  • Run several independent async steps and combine them with Promise.all

Frequently asked questions

Why does my async function return a Promise instead of the value?

Because that is what async functions always do — the return value is wrapped in a promise automatically. Use await at the call site (inside another async function or at the top level of a module) or attach .then() to read the value.

Can I use await outside an async function?

Only at the top level of an ES module, where top-level await is allowed since ES2022. In a classic script or inside a plain function you must wrap the code in an async function, commonly an async IIFE: (async () => { ... })().

Is an async function slower than a regular one?

Only marginally, and only because it allocates a promise and defers the code after each await by one microtask tick. The real performance problem is awaiting inside a loop when the operations could run concurrently via Promise.all.

More examples

Sequential vs. parallel awaits

Both functions produce ["a", "b"], but sequential() awaits each delay in turn so the waits add up, while parallel() starts both before awaiting and finishes in roughly the time of the slower one. Awaiting inside a loop is the usual cause of unexpectedly slow async code.

Example · javascript
const delay = (ms, v) => new Promise((r) => setTimeout(() => r(v), ms));

async function sequential() {
  const a = await delay(60, "a");
  const b = await delay(60, "b");
  return [a, b];
}

async function parallel() {
  return Promise.all([delay(60, "a"), delay(60, "b")]);
}

console.log("sequential took ~120ms:", await sequential());
console.log("parallel took ~60ms:", await parallel());

Errors become rejections

JSON.parse throws, but because it throws inside an async function the error is packaged into the returned promise. The first try block finishes cleanly and prints its message; only the awaited call surfaces the SyntaxError to catch.

Example · javascript
async function parse(json) {
  return JSON.parse(json); // may throw synchronously
}

try {
  parse("{ broken"); // no await: nothing is caught here
  console.log("the try block saw no error");
} catch {
  console.log("never reached");
}

try {
  await parse("{ broken");
} catch (e) {
  console.log("caught once awaited:", e.name);
}

Async methods and arrow functions

async works on object methods, class methods and arrow functions, not just declarations. store.get keeps its own this because it is a method; the arrow function load inherits this from where it is defined. Awaiting null still defers the rest of the body by one microtask tick.

Example · javascript
const store = {
  data: new Map([["a", 1]]),
  async get(key) {
    await null; // yields to the microtask queue
    return this.data.get(key) ?? 0;
  },
};

const load = async (keys) =>
  Promise.all(keys.map((k) => store.get(k)));

console.log(await store.get("a"));      // 1
console.log(await load(["a", "zz"]));  // [1, 0]

Discussion

  • Be the first to comment on this lesson.