Promise 和 async/await:从用途到执行机制
前面复习同步异步时,我们已经知道:
text
Promise.then 是微任务。
async/await 中 await 后面的代码也可以理解成微任务。
但是面试里只知道执行顺序还不够。还需要知道:
text
Promise 是什么?
Promise 有什么用?
async/await 和 Promise 是什么关系?
为什么说 async/await 是 Promise 的语法糖?
1. 为什么需要 Promise
JS 里有很多异步场景:
- 定时器
- 网络请求
- 文件读取
- 用户事件
- 数据库操作
早期处理异步,最常见的是回调函数。
js
setTimeout(function () {
console.log('异步完成');
}, 1000);
这种写法本身没问题。
但如果多个异步任务有先后依赖,就容易变成回调嵌套:
js
getUser(function (user) {
getOrders(user.id, function (orders) {
getOrderDetail(orders[0].id, function (detail) {
console.log(detail);
});
});
});
这种代码可读性差、维护困难,也就是常说的"回调地狱"。
所以 Promise 出现的主要目的就是:
text
用链式调用的方式管理异步流程,解决回调嵌套问题。
2. Promise 是什么
Promise 是一个表示异步操作最终结果的对象。
它可以理解成一个"承诺":
text
现在结果还没出来,但未来一定会变成成功或失败。
Promise 有三种状态:
pending:进行中fulfilled:成功rejected:失败
状态变化只能发生一次:
text
pending -> fulfilled
pending -> rejected
一旦状态改变,就不会再变。
3. Promise 基础语法
js
const promise = new Promise(function (resolve, reject) {
const success = true;
if (success) {
resolve('成功结果');
} else {
reject('失败原因');
}
});
promise
.then(function (res) {
console.log(res);
})
.catch(function (err) {
console.log(err);
})
.finally(function () {
console.log('结束');
});
这里要注意:
text
new Promise 里的执行器函数是同步执行的。
then / catch / finally 里的回调是异步执行的,属于微任务。
比如:
js
console.log('1');
new Promise((resolve) => {
console.log('2');
resolve();
}).then(() => {
console.log('3');
});
console.log('4');
输出:
text
1
2
4
3
4. resolve 和 reject 是干什么的
resolve 用来把 Promise 状态改成成功。
js
resolve('data');
成功后的结果会传给 then:
js
promise.then((data) => {
console.log(data);
});
reject 用来把 Promise 状态改成失败。
js
reject('error');
失败原因会传给 catch:
js
promise.catch((err) => {
console.log(err);
});
5. Promise 链式调用
Promise 支持链式调用。
js
Promise.resolve(1)
.then((res) => {
console.log(res); // 1
return res + 1;
})
.then((res) => {
console.log(res); // 2
return res + 1;
})
.then((res) => {
console.log(res); // 3
});
为什么可以一直 .then()?
因为:
text
then 方法会返回一个新的 Promise。
上一个 then 的返回值,会成为下一个 then 的参数。
如果 then 里返回的是普通值:
js
return 123;
下一个 then 会拿到 123。
如果 then 里返回的是 Promise:
js
return Promise.resolve(123);
下一个 then 会等这个 Promise 完成后,再拿到结果。
6. Promise 常用方法
Promise.resolve
快速创建一个成功状态的 Promise。
js
Promise.resolve('ok').then((res) => {
console.log(res); // ok
});
Promise.reject
快速创建一个失败状态的 Promise。
js
Promise.reject('error').catch((err) => {
console.log(err); // error
});
Promise.all
等待所有 Promise 都成功。
js
Promise.all([requestA(), requestB(), requestC()])
.then((res) => {
console.log(res);
});
特点:
text
全部成功,才会成功。
只要有一个失败,整体就失败。
适合多个请求都完成后再继续。
Promise.race
谁先完成,就用谁的结果。
js
Promise.race([requestA(), requestB()])
.then((res) => {
console.log(res);
});
适合做请求超时控制。
Promise.allSettled
等待所有 Promise 都结束,不管成功还是失败。
js
Promise.allSettled([requestA(), requestB()])
.then((res) => {
console.log(res);
});
适合需要收集所有结果的场景。
7. Promise 解决了什么问题
Promise 主要解决三个问题:
text
1. 解决回调地狱。
2. 统一异步成功和失败的处理方式。
3. 让多个异步任务可以链式组合。
回调写法:
js
getUser(function (user) {
getOrders(user.id, function (orders) {
console.log(orders);
});
});
Promise 写法:
js
getUser()
.then((user) => getOrders(user.id))
.then((orders) => {
console.log(orders);
})
.catch((err) => {
console.log(err);
});
8. async/await 是什么
async/await 是基于 Promise 的异步语法。
它可以让异步代码写起来更像同步代码。
Promise 写法:
js
function getData() {
return fetch('/api/user')
.then((res) => res.json())
.then((data) => {
console.log(data);
});
}
async/await 写法:
js
async function getData() {
const res = await fetch('/api/user');
const data = await res.json();
console.log(data);
}
这两种写法本质上都是 Promise。
区别是:
text
async/await 写法更像同步代码,可读性更好。
9. async 函数有什么特点
只要函数前面加了 async,这个函数就一定返回 Promise。
js
async function fn() {
return 123;
}
console.log(fn());
输出的是一个 Promise:
text
Promise fulfilled 123
等价于:
js
function fn() {
return Promise.resolve(123);
}
所以:
js
async function fn() {
return 123;
}
fn().then((res) => {
console.log(res); // 123
});
10. await 是干什么的
await 用来等待一个 Promise 的结果。
js
async function fn() {
const res = await Promise.resolve('hello');
console.log(res);
}
输出:
text
hello
如果 await 后面不是 Promise,会被包装成 Promise。
js
async function fn() {
const res = await 123;
console.log(res);
}
可以理解成:
js
await Promise.resolve(123);
11. await 前后执行顺序
js
async function fn() {
console.log('1');
await 100;
console.log('2');
}
fn();
console.log('3');
输出:
text
1
3
2
原因:
text
await 前面的代码同步执行。
await 后面的代码会被放到微任务中执行。
可以粗略理解成:
js
async function fn() {
console.log('1');
Promise.resolve(100).then(() => {
console.log('2');
});
}
所以 console.log('3') 会先执行,然后再执行 await 后面的代码。
12. 为什么说 async/await 是语法糖
语法糖的意思是:
text
语法更好写、更好读,但底层能力不是全新的。
async/await 底层仍然是基于 Promise。
比如:
js
async function getUser() {
const user = await requestUser();
const orders = await requestOrders(user.id);
return orders;
}
本质上类似于:
js
function getUser() {
return requestUser()
.then((user) => {
return requestOrders(user.id);
})
.then((orders) => {
return orders;
});
}
所以可以说:
text
async/await 是 Promise 的语法糖。
它没有改变 JS 异步机制,只是让 Promise 链式调用写起来更像同步流程。
13. async/await 大概是怎么实现的
更底层一点看,async/await 可以理解为:
text
Generator 函数 + 自动执行器 + Promise
先看 Generator:
js
function* gen() {
const a = yield Promise.resolve(1);
const b = yield Promise.resolve(a + 1);
return b;
}
Generator 的特点是可以暂停和继续执行。
js
const iterator = gen();
iterator.next(); // 执行到第一个 yield 暂停
iterator.next(1); // 把 1 传回去,继续执行到第二个 yield
iterator.next(2); // 继续执行到 return
async/await 的思想就是:
text
遇到 await,先暂停函数。
等 await 后面的 Promise 完成后,再继续执行后面的代码。
可以用一个简化版自动执行器理解:
js
function run(generatorFn) {
const iterator = generatorFn();
function next(data) {
const result = iterator.next(data);
if (result.done) {
return Promise.resolve(result.value);
}
return Promise.resolve(result.value).then((res) => {
return next(res);
});
}
return next();
}
使用:
js
function* getData() {
const user = yield Promise.resolve({ id: 1 });
const orders = yield Promise.resolve(['order1', 'order2']);
return { user, orders };
}
run(getData).then((res) => {
console.log(res);
});
这就是 async/await 的核心思想:
text
用 Promise 管理异步结果。
用暂停和恢复的机制,让异步代码看起来像同步代码。
面试不一定要手写完整实现,但要知道:
text
async/await 本质上是 Promise 的语法糖,早期可以用 Generator + Promise 自动执行器模拟。
14. 错误处理
Promise 用 catch 处理错误:
js
requestUser()
.then((user) => {
return requestOrders(user.id);
})
.catch((err) => {
console.log(err);
});
async/await 用 try...catch 处理错误:
js
async function getData() {
try {
const user = await requestUser();
const orders = await requestOrders(user.id);
console.log(orders);
} catch (err) {
console.log(err);
}
}
这也是 async/await 更像同步代码的地方。
15. Promise 和 async/await 的关系
可以这样总结:
text
Promise 是 JS 处理异步结果的一种对象和规范。
async/await 是基于 Promise 的更舒服的写法。
它们不是两个完全独立的东西。
async 函数返回 Promise:
js
async function fn() {
return 'hello';
}
等价于:
js
function fn() {
return Promise.resolve('hello');
}
await 等待 Promise:
js
const data = await request();
类似于:
js
request().then((data) => {
// 后续代码
});
16. 一道综合执行顺序题
js
console.log('1');
async function async1() {
console.log('2');
await async2();
console.log('3');
}
async function async2() {
console.log('4');
}
setTimeout(() => {
console.log('5');
}, 0);
new Promise((resolve) => {
console.log('6');
resolve();
}).then(() => {
console.log('7');
});
async1();
console.log('8');
分析:
同步任务:
text
1
6
2
4
8
微任务:
text
7
3
宏任务:
text
5
最终输出:
text
1
6
2
4
8
7
3
5
注意:
text
async2 虽然是 async 函数,但它里面 await 前面的代码仍然是同步执行。
await 后面的 console.log('3') 会进入微任务。
17. 面试怎么答
如果面试官问:
text
说一下 Promise。
可以这样答:
text
Promise 是 JS 处理异步的一种方案,它表示一个异步操作最终成功或失败的结果。Promise 有 pending、fulfilled、rejected 三种状态,状态一旦从 pending 变成 fulfilled 或 rejected 就不能再改变。Promise 通过 then、catch、finally 处理成功、失败和结束逻辑,并且 then 会返回新的 Promise,所以可以链式调用,从而解决回调地狱问题。
如果面试官问:
text
说一下 async/await。
可以这样答:
text
async/await 是 Promise 的语法糖,用同步写法来表达异步流程。async 函数一定返回 Promise,await 用来等待 Promise 的结果。await 前面的代码同步执行,await 后面的代码会作为微任务执行。它本质上没有改变 JS 的异步机制,只是让 Promise 链式调用更清晰,早期可以用 Generator + Promise 自动执行器来模拟。
18. 最后总结
Promise 解决的是:
text
异步结果如何表示和组合。
async/await 解决的是:
text
异步代码如何写得更像同步代码。
两者关系:
text
Promise 是基础。
async/await 是基于 Promise 的语法糖。
执行顺序:
text
new Promise 执行器同步执行。
then / catch / finally 是微任务。
async 函数返回 Promise。
await 前面同步执行。
await 后面进入微任务。