简单来说,就是让异步代码看起来像同步。是 JavaScript 中基于 Promise 的处理异步操作的一种语法糖。
背景
回调地狱 (Callback Hell)
1 2 3 4 5 6 7 8 9
| getData(function (a) { getMoreData(a, function (b) { getEvenMoreData(b, function (c) { getFinalData(c, function (result) { console.log(result); }); }); }); });
|
全是缩进,可读性差。
后来 ES6 中出现 Promise 的链式调用
1 2 3 4 5 6
| getData() .then((a) => getMoreData(a)) .then((b) => getEvenMoreData(b)) .then((c) => getFinalData(c)) .then((result) => console.log(result)) .catch((err) => console.error(err));
|
但是尽管如此,还是有条件判断麻烦, try/catch 捕捉不到 Promise 内部错误等缺陷。
于是,像阻塞但是实际不阻塞的语法出现,同时可读性大大提升。
主要原理
async/await 的设计思想类很似 Generator + co,但并不是基于 Generator 实现的。
它是 V8 引擎原生支持的特性,性能更好,机制更直接。
也就是,await 把后续代码注册成 Promise 的 .then 回调,放入微任务队列。(交给 Promise 处理)
1 2 3 4 5 6 7 8 9 10
| console.log("1");
async function foo() { console.log("2"); await Promise.resolve(); console.log("3"); }
foo(); console.log("4");
|
await Promise.resolve()会把 console.log('3')放进微任务队列。
当前同步代码 console.log('4')执行完后,事件循环才处理微任务。
这就是 await 的真相:
它不是真的暂停,而是把后续逻辑放进微任务,等当前同步代码执行完再执行。
基本用法
1 2 3 4 5 6 7 8 9
| async function fetchData() { try { const response = await fetch("/api/user"); const user = await response.json(); console.log(user); } catch (error) { console.error("出错了:", error); } }
|
async 返回的是 Promise。如果 return 一个值,会自动包装成 Promise.resolve(value)。如果 throw 错误,会变成 Promise.reject(error)
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| async function hello() { return "world"; }
hello();
hello().then(console.log);
async function errorFunc() { throw new Error("boom!"); }
errorFunc().catch((err) => console.log(err.message));
|
await 只能在 async 中使用,同时 await 等的是一个 Promise。如果不是 Promise,也会自动包装成 Promise.resolve()。
1 2 3 4
| const result = await somePromise();
const num = await 42; console.log(num);
|
怎么用
多个接口全部返回
Promise.all 同时发起三个请求,谁也不等谁。等全部 resolve,再一起返回。Promise.all 是“全成功才成功”,任何一个 reject,整个就 reject。
如果希望“失败也不影响”,用 Promise.allSettled。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| async function bad() { const a = await fetchA(); const b = await fetchB(); const c = await fetchC(); }
async function good() { const [a, b, c] = await Promise.all([fetchA(), fetchB(), fetchC()]); }
const results = await Promise.allSettled([fetchA(), fetchB(), fetchC()]);
results.forEach((result) => { if (result.status === "fulfilled") { console.log(result.value); } else { console.log("失败:", result.reason); } });
|
条件判断 + 异步
用户登录后,先查权限,再决定加载哪个页面。(用 Promise 链写,嵌套 .then 里的 .then。)
1 2 3 4 5 6 7 8 9 10 11
| async function loadPage() { const user = await fetchUser();
if (user.isAdmin) { const data = await fetchAdminData(); renderAdminPage(data); } else { const data = await fetchUserData(); renderUserPage(data); } }
|
循环 + await
1 2 3 4 5 6 7 8 9 10 11 12
| async function Loop1() { const ids = [1, 2, 3, 4, 5]; for (let id of ids) { const user = await fetchUser(id); process(user); } }
async function Loop2() { const ids = [1, 2, 3, 4, 5]; await Promise.all(ids.map((id) => fetchUser(id))); }
|
需要注意
忘记 try/catch
如果 fetch 失败,这个函数会抛出异常。但没接,就变成未捕获的 Promise rejection。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| async function forgotCatch() { const res = await fetch("/api/data"); return res.json(); }
async function safeFetch() { try { const res = await fetch("/api/data"); const data = await res.json(); return data; } catch (err) { console.error("请求失败:", err); return null; } }
safeFetch().catch((err) => console.log(err));
|
forEach/map 中 await 无效
map 的回调是 async 函数,返回的是 Promise。但 map 本身不会 await 这些 Promise。所以这些请求是并发的,但主流程不等它们。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| const urls = ["/a", "/b", "/c"];
urls.map(async (url) => { const res = await fetch(url); console.log(await res.text()); });
console.log("done");
for (let url of urls) { const res = await fetch(url); console.log(await res.text()); } console.log("done");
await Promise.all( urls.map(async (url) => { const res = await fetch(url); console.log(await res.text()); }) );
|
其他用法
异步迭代器 (处理数据流)
1 2 3 4 5
| async function processStream(stream) { for await (const chunk of stream) { await processChunk(chunk); } }
|
自动重试
1 2 3 4 5 6 7 8 9 10 11
| async function fetchWithRetry(url, retries = 3) { for (let i = 0; i < retries; i++) { try { const response = await fetch(url); return await response.json(); } catch (error) { if (i === retries - 1) throw error; await sleep(1000 * (i + 1)); } } }
|
超时控制
1 2 3 4 5 6 7 8
| async function fetchWithTimeout(url, timeout = 5000) { const fetchPromise = fetch(url); const timeoutPromise = new Promise((_, reject) => { setTimeout(() => reject(new Error("请求超时")), timeout); });
return await Promise.race([fetchPromise, timeoutPromise]); }
|
推荐阅读