async / await 深入
约 2297 字大约 8 分钟
2026-04-16
1. async/await 与 Promise 的关系
async/await 是 Promise 的语法糖,它建立在 Promise 之上,提供了更同步化的编码方式。
// Promise 写法
function fetchData() {
return fetch('/api/data')
.then(res => res.json())
.then(data => console.log(data));
}
// async/await 写法(完全等价)
async function fetchData() {
const res = await fetch('/api/data');
const data = await res.json();
console.log(data);
}核心关系:
async函数始终返回一个 Promiseawait只能在 async 函数内部使用await等待右侧 Promise resolved 后的值async/await不会改变 Promise 的本质,只是让异步代码看起来像同步代码
2. async 函数的返回值
async 函数的返回值会被自动包装成 Promise。
// 返回基本类型
async function getNumber() {
return 42;
}
getNumber(); // Promise { <state>: "fulfilled", <value>: 42 }
// 返回 Promise(不进行二次包装)
async function getPromise() {
return Promise.resolve(100);
}
getPromise(); // Promise { <state>: "fulfilled", <value>: 100 }
// 抛出错误
async function throwError() {
throw new Error('Oops!');
}
throwError(); // Promise { <state>: "rejected", <reason>: Error: Oops! }
// 返回值会被解析,未定义也会被包装
async function returnUndefined() {
// 没有 return 语句
}
returnUndefined(); // Promise { <state>: "fulfilled", <value>: undefined }3. await 的执行机制
3.1 await 会阻塞吗?
从微观角度看,await 会暂停当前 async 函数的执行,让出 JS 执行线程。但在事件循环层面,它不会阻塞主线程,页面仍能响应其他事件。
async function example() {
console.log('1');
await fetch('/api'); // 暂停,等待完成
console.log('2'); // 继续执行
}3.2 微任务队列的关系
await 会把后续代码包装成微任务,放入微任务队列。
async function microtaskDemo() {
console.log('1');
await Promise.resolve();
console.log('2');
await new Promise(resolve => setTimeout(resolve, 0));
console.log('4');
}
console.log('start');
microtaskDemo();
console.log('3');
// 输出顺序:start -> 1 -> 3 -> 2 -> 4执行过程分析:
console.log('start')同步执行- 调用
microtaskDemo() console.log('1')同步执行await Promise.resolve()创建一个微任务(后续代码)microtaskDemo()返回 Promise,主线程继续console.log('3')同步执行- 事件循环取出微任务,执行
console.log('2') - 第二个
await中的setTimeout是宏任务,0ms 后触发 - 宏任务完成后,其后的
console.log('4')作为微任务执行
3.3 错误处理(try/catch)
// try/catch 捕获异常
async function handleError() {
try {
const data = await fetch('/api');
const result = await data.json();
console.log(result);
} catch (error) {
console.error('请求失败:', error);
}
}
// 捕获 await 链中任意环节的错误
async function chainError() {
try {
await step1(); // 错误在这里抛出
await step2(); // 不会执行
} catch (e) {
console.log('被捕获:', e.message);
}
}
// 多个 await 需要单独 try/catch 或统一捕获
async function multiAwait() {
try {
const [a, b, c] = await Promise.all([
fetch('/a').then(r => r.json()),
fetch('/b').then(r => r.json()),
fetch('/c').then(r => r.json())
]);
} catch (e) {
// 任一请求失败都会进入这里
}
}
// finally 不受 await 影响
async function withFinally() {
try {
await fetch('/api');
} catch (e) {
console.error(e);
} finally {
console.log('清理工作'); // 始终执行
}
}4. async/await vs Promise.then() 的堆栈处理差异
这是 async/await 最重要的性能优势之一。
// Promise.then() 版本
const a = () => {
b().then(() => c());
};当 a() 执行时:
b()被调用,返回 Promise.then()的回调被加入微任务队列a()函数执行完毕,作用域即将销毁- 当 Promise resolve 时,
a()的作用域已不存在
问题:要打印包含 a() 的堆栈信息,JavaScript 引擎必须额外记录和保存堆栈上下文。
// async/await 版本
const a = async () => {
await b();
c();
};当 b() 执行时:
a()函数被暂停,但仍在内存中- 无需存储完整堆栈,只需存储
a()的引用指针 c()执行时,如果出错,堆栈可以直接生成
对比总结:
| 特性 | Promise.then() | async/await |
|---|---|---|
| 堆栈存储 | 需要额外保存完整堆栈信息 | 只需保存指针,内存占用小 |
| 错误堆栈 | 跨多个 .then() 链式调用时堆栈分散 | 堆栈信息清晰连续 |
| 性能 | 保存/传递堆栈有额外开销 | 更接近同步函数的执行模型 |
| 调试 | 断点位置跳跃,不直观 | 可像同步代码一样单步调试 |
5. 常见错误和最佳实践
5.1 常见错误
// 错误1:忘记 return await(在循环中常见)
async function badLoop() {
const nums = [1, 2, 3];
for (const num of nums) {
await process(num); // 串行执行,若想并行应该用 Promise.all
}
}
// 错误2:并发需求却串行执行
async function badParallel() {
const a = await fetchA(); // 等待 A 完成才去请求 B
const b = await fetchB();
return [a, b];
}
// 正确做法
async function goodParallel() {
const [a, b] = await Promise.all([fetchA(), fetchB()]);
return [a, b];
}
// 错误3:await 在 map 中形成串行
async function badMap() {
const items = [1, 2, 3];
const results = items.map(async (item) => {
return await process(item); // 实际上是串行执行
});
return Promise.all(results);
}
// 错误4:没有正确处理 reject
async function unhandledReject() {
await Promise.reject(new Error('Oops')); // 若不 catch,全局 unhandledrejection
// 应该用 try/catch 包裹
}5.2 最佳实践
// 实践1:及时 await,避免长时间挂起
async function timelyAwait() {
// 好的做法
const data = await fetchData();
return process(data);
// 不好的做法:创建了 Promise 但不及时 await
const promise = fetchData(); // 这里没有阻塞,但也不应该无限期拖延
// ... 一些其他同步代码 ...
return await promise; // 或者在这里才 await
}
// 实践2:并行使用 Promise.all,集中处理错误
async function parallelBestPractice() {
try {
const [users, posts] = await Promise.all([
fetch('/users').then(r => r.json()),
fetch('/posts').then(r => r.json())
]);
return { users, posts };
} catch (error) {
console.error('获取数据失败:', error);
throw error; // 或返回默认值
}
}
// 实践3:使用 Promise.allSettled 处理部分失败
async function partialFailure() {
const results = await Promise.allSettled([
fetch('/api1').then(r => r.json()),
fetch('/api2').then(r => r.json()),
fetch('/api3').then(r => r.json())
]);
results.forEach((result, index) => {
if (result.status === 'fulfilled') {
console.log(`API${index + 1} 成功:`, result.value);
} else {
console.error(`API${index + 1} 失败:`, result.reason);
}
});
}
// 实践4:避免在循环中不必要的 await
async function loopOptimization(items) {
// 串行:每个完成后才执行下一个
for (const item of items) {
await process(item);
}
// 并行:一次性发起所有请求
await Promise.all(items.map(item => process(item)));
}
// 实践5:顶层 await(ES2022)
// 在模块顶层直接使用 await
const data = await fetch('/api/config');
const response = await data.json();
export { config: response };6. 并行执行:Promise.all / Promise.allSettled / Promise.race 与 async/await 结合
6.1 Promise.all
所有 Promise 都 resolved 才算成功,任一 reject 则整体 reject。
async function fetchAllUsers(userIds) {
// 并行请求
const promises = userIds.map(id => fetch(`/user/${id}`).then(r => r.json()));
const users = await Promise.all(promises);
return users;
}
// 实际应用场景
async function initializeApp() {
const [user, settings, notifications] = await Promise.all([
fetchCurrentUser(),
fetchSettings(),
fetchNotifications()
]);
return { user, settings, notifications };
}6.2 Promise.allSettled
无论成功失败都返回结果,适用于"尽可能获取,多个失败不影响其他"的场景。
async function loadDashboardWidgets() {
const results = await Promise.allSettled([
fetchStatistics(), // 可能失败
fetchRecommendations(), // 可能失败
fetchWeather() // 可能失败
]);
return results.map((result, index) => {
if (result.status === 'fulfilled') {
return { widget: index, data: result.value, error: null };
}
return { widget: index, data: null, error: result.reason };
});
}6.3 Promise.race
返回最先 settled(resolved 或 rejected)的 Promise 的结果。
async function raceDemo() {
const timeout = new Promise((_, reject) =>
setTimeout(() => reject(new Error('请求超时')), 5000)
);
const request = fetch('/api/slow-endpoint');
try {
const result = await Promise.race([request, timeout]);
console.log('快速响应:', result);
} catch (error) {
console.error('超时或请求失败:', error.message);
}
}
// 实际应用:cdn 加载加速(选用最快的 cdn)
async function loadFromFastestCDN() {
const cdns = [
'https://cdn1.example.com/library.js',
'https://cdn2.example.com/library.js',
'https://cdn3.example.com/library.js'
];
const loadScript = (url) =>
new Promise((resolve, reject) => {
const script = document.createElement('script');
script.onload = () => resolve(url);
script.onerror = () => reject(new Error(`Failed to load: ${url}`));
script.src = url;
document.head.appendChild(script);
});
const fastest = await Promise.race(
cdns.map(url => loadScript(url).then(() => url))
);
console.log(`最快的 CDN: ${fastest}`);
}6.4 Promise.any
返回第一个 resolved 的结果,全部 reject 才失败(ES2021)。
async function anyDemo() {
const endpoints = [
'https://api1.example.com/data',
'https://api2.example.com/data',
'https://api3.example.com/data'
];
try {
const result = await Promise.any(
endpoints.map(url => fetch(url).then(r => r.json()))
);
console.log('第一个成功的响应:', result);
} catch (error) {
console.error('所有请求都失败了:', error.errors);
}
}7. 模拟实现 async/await(Generator + 自动执行器)
async/await 的本质是 Generator + 自动执行器的语法糖。理解其实现原理有助于深入掌握异步编程。
7.1 Generator 基础回顾
function* genDemo() {
console.log('开始');
yield 1;
console.log('继续');
yield 2;
console.log('结束');
return 3;
}
const gen = genDemo();
console.log(gen.next()); // { value: 1, done: false },输出"开始"
console.log(gen.next()); // { value: 2, done: false },输出"继续"
console.log(gen.next()); // { value: 3, done: true },输出"结束"7.2 自动执行器
将 Generator 函数自动执行,直到结束。
function runGenerator(gen) {
const generator = gen();
function iterate(nextValue) {
const result = generator.next(nextValue);
if (result.done) {
return result.value;
}
// 如果是 Promise,等待其 resolve 后继续
if (result.value instanceof Promise) {
return result.value.then(value => iterate(value));
}
return iterate(result.value);
}
return iterate();
}
// 使用
runGenerator(function* () {
const a = yield Promise.resolve(1);
const b = yield Promise.resolve(2);
const c = yield Promise.resolve(3);
return a + b + c;
}); // 返回 Promise,resolve 后得到 67.3 完整实现 async/await
// 自动执行器
function run(gen) {
const generator = gen();
return new Promise((resolve, reject) => {
function step(key, arg) {
let result;
try {
result = generator[key](arg);
} catch (error) {
return reject(error);
}
const { value, done } = result;
if (done) {
return resolve(value);
}
// 处理 Promise
if (value instanceof Promise) {
value
.then(val => step('next', val))
.catch(err => step('throw', err));
} else {
step('next', value);
}
}
step('next');
});
}
// 测试
run(function* () {
try {
const user = yield fetch('/api/user').then(r => r.json());
const posts = yield fetch('/api/posts').then(r => r.json());
return { user, posts };
} catch (error) {
console.error('Error:', error);
}
});
// async/await 语法糖的转换
// 编译器将 async function 转换为类似这样的结构:
async function myAsyncFunction() {
try {
const result = await somePromise();
return result;
} catch (e) {
console.error(e);
}
}
// 编译后(简化表示)
function myAsyncFunction() {
return run(function* () {
try {
const result = yield somePromise();
return result;
} catch (e) {
console.error(e);
}
});
}7.4 async 函数的错误处理原理
// async 函数中的 try/catch 在 Generator 中如何实现
async function withTryCatch(url) {
try {
const res = await fetch(url);
return await res.json();
} catch (err) {
console.error('请求失败:', err);
return null;
}
}
// Generator 等价形式
function withTryCatch(url) {
return run(function* () {
try {
const res = yield fetch(url);
return yield res.json();
} catch (err) {
console.error('请求失败:', err);
return null;
}
});
}