Promise 与 async/await

70 阅读4分钟

🔥 Promise

Promise 是一个“未来值”的状态机 + 异步回调容器。

主要做三件大事:

  1. 把异步结果封进一个对象(pending → fulfilled/rejected)
  2. 状态只能单向流转
  3. 所有回调(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。

具体来说:

  1. async 会把返回值自动包成 Promise
  2. await 会“暂停”当前 async 函数,把后续拆成 then
  3. 并不会阻塞线程,只是代码被拆开
  4. 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));

🎯 两者的区别

维度Promiseasync/await
风格链式回调同步化写法
本质状态机 + 回调队列Promise 语法糖
错误处理then/catchtry/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)

执行流程分析

  1. 同步代码执行顺序
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. 事件循环分析
时间轴:
├── 同步代码执行
│   ├── 打印 1
│   ├── 打印 2 (Promise 构造函数内)
│   ├── 打印 4 (test 函数内的同步代码)
│   └── 打印 6
│
└── 1秒后 (setTimeout 回调)
    ├── 生成 random
    ├── 执行 resolve/reject
    ├── 打印 3
    └── 触发 .then/.catch 回调
        └── 打印 5
  1. 详细解释

  2. console.log(1):第一个执行,打印 1

  3. new Promise: · Promise 构造函数是同步执行的 · 立即打印 2 · setTimeout 的回调被放入宏任务队列

  4. test() 调用: · 打印 4(同步代码) · await promiseDemo:遇到 await,暂停函数执行,返回控制权

  5. console.log(6):最后一个同步代码,打印 6

  6. 同步代码执行完毕,开始处理异步任务

  7. 1秒后: · setTimeout 回调执行 · 根据 random 值决定 resolve/reject · 打印 3 · promise 状态变为 resolved/rejected

  8. 微任务执行: · promise 状态变更后,对应的 .then/.catch 回调作为微任务执行 · 打印 5

  9. 最终打印结果

无论成功或失败,打印顺序都是:

1
2
4
6
3
5
  1. 关键知识点

  2. Promise 构造函数是同步执行的,所以 2 立即打印

  3. await 会暂停 async 函数,但不会阻塞外部代码,所以 6 在 4 之后立即打印

  4. setTimeout 是宏任务,1 秒后执行

  5. .then/.catch 是微任务,在 promise 状态变更后立即执行

  6. 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捕获到