An async function always returns a promise: return x fulfills it and throw rejects it. await p suspends the function and returns control to the caller. When p settles, the function resumes as a microtask: await yields the value or throws the reason, so try...catch works. Arrows (async x => ...) and methods can be async too.
const delay = (ms, value) => new Promise(resolve => setTimeout(resolve, ms, value));
async function time(label, fn) {
const start = performance.now(), result = await fn();
console.log(label, result, `~${Math.round((performance.now() - start) / 100) * 100} ms`);
}
await time("sequential", async () => (await delay(100, "a")) + (await delay(100, "b")));
const both = () => Promise.all([delay(100, "a"), delay(100, "b")]); // both timers start now
await time("concurrent", async () => (await both()).join(""));
async function load() {
try { await Promise.reject(new Error("offline")); }
catch (err) { return `fallback: ${err.message}`; }
}
console.log(await load());sequential ab ~200 ms concurrent ab ~100 ms fallback: offline
Start independent requests together instead of awaiting them one by one. array.forEach(async ...) waits for nothing; use for...of with await, or Promise.all(array.map(...)). Since ECMAScript 2022, modules may await at the top level.