🔥 Promise
Promise 是一个“未来值”的状态机 + 异步回调容器。
主要做三件大事:
- 把异步结果封进一个对象(pending → fulfilled/rejected)
- 状态只能单向流转
- 所有回调(then/catch)都被塞进 microtask 队列执行
所以 Promise 并不是“执行异步”,而是“组织和调度异步”。
Promise 构造函数是同步执行的
⚠️ Promise 常见踩坑
❌ 1. 链式没有自动中断
Promise.resolve()
.then(() => { throw new Error('oops') })
.then(() => console.log('still?'))
要想中断链式调用,必须 return rejected promise
❌ 2. then 的 return ≠ 外层函数 return
function a() {
return Promise.resolve().then(() => 123)
}
console.log(a()) // 打印的永远是 Promise
❌ 3. await 写法不一定并发
await foo();
await bar(); // → 并没有并发,纯串行
正确并发:
await Promise.all([foo(), bar()]);
❌ 4. Promise 内 throw 与外部 throw 不是一回事
内部 throw → rejected
外部 throw → 程序直接炸
⚡ async/await
async/await = 用同步语法包装异步逻辑。 底层仍然是 Promise + microtask。
具体来说:
- async 会把返回值自动包成 Promise
- await 会“暂停”当前 async 函数,把后续拆成 then
- 并不会阻塞线程,只是代码被拆开
- async/await 不改变事件循环本质
⚠️ async/await 的常见踩坑
❌ 1. await 会让并发变串行
await foo();
await bar();
❌ 2. await 不会自动捕获异常
await Promise.reject('err'); // 不 try/catch 会直接抛
❌ 3. async 函数返回的一定是 Promise
async function a() { return 1; }
❌ 4. forEach + await = 陷阱现场
arr.forEach(async item => { await do(item); });
→ forEach 不会等你。
正确写法:
顺序执行:
for (const item of arr) await do(item);
并行执行:
await Promise.all(arr.map(do));
🎯 两者的区别
| 维度 | Promise | async/await |
|---|---|---|
| 风格 | 链式回调 | 同步化写法 |
| 本质 | 状态机 + 回调队列 | Promise 语法糖 |
| 错误处理 | then/catch | try/catch |
| 可读性 | 容易嵌套 | 清晰直观 |
| 并发 | Promise.all/race | 仍需配合 Promise |
| 堆栈 | 更碎 | 更连续 |
Promise 解决异步结果表达;async/await 解决异步写法难看。
Promise 是底层机制,async/await 是表现形式
异步世界里,它俩谁都离不开谁。 juejin.cn/post/684490…
console.log(1)
let promiseDemo = new Promise((resolve, reject) => {
console.log(2)
setTimeout(() => {
let random = Math.random()
if (random >= 0.2) {
resolve('success')
console.log(3)
} else {
reject('failed')
console.log(3)
}
}, 1000)
})
async function test() {
console.log(4)
let result = await promiseDemo
return result
}
test().then(result => {
console.log(5)
}).catch((result) => {
console.log(5)
})
console.log(6)
执行流程分析
- 同步代码执行顺序
console.log(1) // 第1个打印:1
let promiseDemo = new Promise((resolve, reject) => {
console.log(2) // 第2个打印:2
// setTimeout 的回调是异步的,稍后执行
})
async function test() {
console.log(4) // 这个会在 test() 调用时执行
let result = await promiseDemo
return result
}
test() // 调用 test 函数
console.log(6) // 第3个打印:6
- 事件循环分析
时间轴:
├── 同步代码执行
│ ├── 打印 1
│ ├── 打印 2 (Promise 构造函数内)
│ ├── 打印 4 (test 函数内的同步代码)
│ └── 打印 6
│
└── 1秒后 (setTimeout 回调)
├── 生成 random
├── 执行 resolve/reject
├── 打印 3
└── 触发 .then/.catch 回调
└── 打印 5
-
详细解释
-
console.log(1):第一个执行,打印 1
-
new Promise: · Promise 构造函数是同步执行的 · 立即打印 2 · setTimeout 的回调被放入宏任务队列
-
test() 调用: · 打印 4(同步代码) · await promiseDemo:遇到 await,暂停函数执行,返回控制权
-
console.log(6):最后一个同步代码,打印 6
-
同步代码执行完毕,开始处理异步任务
-
1秒后: · setTimeout 回调执行 · 根据 random 值决定 resolve/reject · 打印 3 · promise 状态变为 resolved/rejected
-
微任务执行: · promise 状态变更后,对应的 .then/.catch 回调作为微任务执行 · 打印 5
-
最终打印结果
无论成功或失败,打印顺序都是:
1
2
4
6
3
5
-
关键知识点
-
Promise 构造函数是同步执行的,所以 2 立即打印
-
await 会暂停 async 函数,但不会阻塞外部代码,所以 6 在 4 之后立即打印
-
setTimeout 是宏任务,1 秒后执行
-
.then/.catch 是微任务,在 promise 状态变更后立即执行
-
3 一定在 5 之前打印,因为 resolve/reject 执行时立即打印 3,然后才触发微任务打印 5
这个例子很好地展示了同步代码、宏任务(setTimeout)、微任务(promise.then)的执行顺序。
const p3 = new Promise((resolve, reject) => {
setTimeout(() => {
throw('error');
});
}).then((res) => {
console.log('p3 res:' + res);
console.log(p3);
}).catch((err) => {
console.log('p3 err:' + err);
console.log(p3);
});
promise 结构中异步逻辑抛出异常不会被catch捕获到