Loading data, reading a file, calling an API: these operations take time, and JavaScript refuses to sit still while they finish. For years the answer was stacking callbacks, with code written in an order that no longer matched the order you read it in.
async fixed that presentation problem. The code reads again as a sequence of instructions from top to bottom, while the engine keeps working in its own way underneath.
Definition
async goes in front of a function and changes two things: the function now always returns a Promise, and the await keyword becomes usable inside its body.
async function loadProfile(id) {
const response = await fetch('/api/profile/' + id);
return response.json();
}
loadProfile(7).then(profile => console.log(profile));The await line looks like an ordinary call, when in fact it pauses the function and hands control back to the rest of the program until the answer arrives.
What an async function always returns
This is the rule that surprises people most: whatever you write after return, calling the function produces a promise.
| Declaration | What the call produces |
|---|---|
function f() { return 42 } | 42 |
async function f() { return 42 } | a promise fulfilled with 42 |
async function f() { throw ... } | a rejected promise |
async function answer() {
return 42;
}
console.log(answer()); // Promise { 42 }
answer().then(v => console.log(v)); // 42A value showing up as Promise in the console almost always comes from this: the function was called without await in front of it.
The four places it is written
- In front of a declaration:
async function load() {}. - In front of an arrow function:
const load = async () => {}. - In front of a method, inside a class or an object:
async load() {}. - In front of a function called immediately:
(async () => { ... })(), handy for opening an asynchronous area inside a file that is not one.
A throw inside an async function does not surface as an ordinary error: it rejects the returned promise. So you catch it with .catch(), or with a try around the call, provided that call is preceded by await.
Frequently asked questions
Should every function be async?
No. An async function containing no await brings nothing: it merely wraps its result in a promise, forcing every caller to adapt. Keep the keyword for functions that genuinely wait for something.
Does async slow the code down?
The keyword's own cost is negligible. What costs is how you chain the waiting: three calls awaited one after another take the sum of their durations, while starting them together with Promise.all() takes the duration of the slowest. That is where the performance lever sits, never in the keyword itself.
Can I use await without async?
Yes, at the top level of a module, where await can be written directly without wrapping the code in a function. Everywhere else, in a classic script in particular, an async function is required. Our JavaScript course walks through this chain starting from promises, the step many people skip before running into these error messages.