async/await 原理与 Promise 对照
1. 核心原理
async/await 是基于 Promise 的语法糖。它没有改变 JavaScript 的异步模型,只是让 Promise 链可以写得更接近同步代码。
async:保证函数返回一个 Promise。
await:等待一个 Promise 的结果,并暂停当前 async 函数后续代码的执行。
await 后面的代码:相当于放进 .then() 回调,会在微任务中继续执行。
try/catch:相当于 Promise 链末尾的 .catch()。
先看一个最小对照。
async/await 写法
async function getUserName() {
const user = await getUser();
return user.name;
}
Promise 写法
function getUserName() {
return getUser().then((user) => {
return user.name;
});
}
两段代码的核心流程完全相同:
调用 getUser()
↓
等待 Promise 成功
↓
读取 user.name
↓
返回一个以 user.name 为结果的 Promise
2. 执行顺序对照
面试中经常考 await 前后代码的执行时机。
async/await 写法
console.log('script start');
async function async1() {
console.log('async1 start');
await async2();
console.log('async1 end');
}
async function async2() {
console.log('async2');
}
async1();
console.log('script end');
等价的 Promise 写法
console.log('script start');
function async1() {
console.log('async1 start');
return async2().then(() => {
console.log('async1 end');
});
}
function async2() {
console.log('async2');
return Promise.resolve();
}
async1();
console.log('script end');
两段代码的输出相同:
script start
async1 start
async2
script end
async1 end
执行过程如下:
- 输出
script start。
- 调用
async1(),同步输出 async1 start。
- 调用
async2(),函数体立即执行,同步输出 async2。
await 后面的代码等价于 .then() 回调,被加入微任务队列。
- 主线程继续执行,同步输出
script end。
- 当前同步代码结束后,执行微任务,输出
async1 end。
关键不是“看到 await 就立即异步”,而是:
await 右侧的表达式会先执行,await 后面的代码才会延后到微任务中执行。
3. 错误处理对照
async/await 可以用 try/catch 统一处理多个异步步骤的错误。
async/await 写法
async function fetchUserPosts() {
try {
const user = await getUser();
const posts = await getPosts(user.id);
return posts;
} catch (error) {
// 可以捕获 getUser 或 getPosts 的失败
console.error(error);
return [];
}
}
等价的 Promise 写法
function fetchUserPosts() {
return getUser()
.then((user) => {
return getPosts(user.id);
})
.then((posts) => {
return posts;
})
.catch((error) => {
// 可以捕获 getUser 或 getPosts 的失败
console.error(error);
return [];
});
}
对应关系是:
| async/await | Promise |
|---|
await getUser() | getUser().then(...) |
第二个 await | 第二个 .then(...) |
try/catch | .catch(...) |
return posts | 在 .then() 中 return posts |
如果希望调用方继续处理错误,两种写法都不要在当前函数中把错误“吞掉”:
// async/await:捕获后重新抛出
catch (error) {
console.error(error);
throw error;
}
// Promise:捕获后返回 rejected Promise
.catch((error) => {
console.error(error);
return Promise.reject(error);
});
4. 串行执行对照
当 B 必须等 A 完成后才能开始时,需要串行执行。
async/await 写法
async function serial() {
const a = await fetchA();
const b = await fetchB();
return [a, b];
}
等价的 Promise 写法
function serial() {
return fetchA().then((a) => {
return fetchB().then((b) => {
return [a, b];
});
});
}
执行过程是:
开始请求 A → A 完成 → 开始请求 B → B 完成
两种写法中,fetchB() 都是在 A 完成后才被调用,因此总耗时大约是两个请求耗时之和。
5. 并行执行对照
如果 A 和 B 互不依赖,应当同时启动。
async/await 写法
async function parallel() {
const promiseA = fetchA();
const promiseB = fetchB();
const a = await promiseA;
const b = await promiseB;
return [a, b];
}
等价的 Promise 写法
function parallel() {
const promiseA = fetchA();
const promiseB = fetchB();
return Promise.all([promiseA, promiseB]);
}
也可以在 async 函数中直接使用 Promise.all:
async function parallel() {
const [a, b] = await Promise.all([fetchA(), fetchB()]);
return [a, b];
}
执行过程是:
开始请求 A ─────→ A 完成
开始请求 B ───────────→ B 完成
↓
得到全部结果
这里实现并行的关键不是 await,而是 fetchA() 和 fetchB() 在等待结果之前就都已经被调用。
6. 返回值与异常对照
返回普通值
// async/await 写法
async function getValue() {
return 42;
}
// 等价的 Promise 写法
function getValue() {
return Promise.resolve(42);
}
两种写法调用后得到的都是一个以 42 为结果的 Promise。
抛出异常
// async/await 写法
async function fail() {
throw new Error('失败');
}
// 等价的 Promise 写法
function fail() {
return Promise.reject(new Error('失败'));
}
两种写法调用后得到的都是 rejected Promise,而不是直接把异常抛给函数调用处。
7. 面试总结
可以把常见写法记成下面的对应关系:
| async/await | Promise |
|---|
async function | 返回 Promise 的普通函数 |
const value = await promise | promise.then((value) => ...) |
try/catch | .catch() |
return value | Promise.resolve(value) |
throw error | Promise.reject(error) |
| 并行等待多个任务 | Promise.all() |
面试时可以这样回答:
async/await 是基于 Promise 的语法糖。async 函数一定返回 Promise;await 会先执行右侧表达式,再暂停当前函数,把后续代码放到微任务中继续执行。连续的 await 对应连续的 .then(),try/catch 对应 .catch()。如果多个异步任务互不依赖,应先同时启动,再使用 Promise.all() 等待结果,避免无意中串行执行。
补充:ES2022 允许在 ES Module 顶层使用 await,无需包裹在 async 函数中。