返回首页

关于Promise

布莱克2026-05-13 21:20已编辑
Tip:文章封面与内容无关,作者旅游时拍摄,因为没什么值得把四季都错过!

Promise

promise 是ES6的一个内置对象 通常用来解决异步调用的问题 解决多层回调嵌套的问题 提高代码的可读性

Promise 的核心是一个状态机,它有且仅有三种状态:

  1. Pending(进行中):初始状态,既没有成功,也没有失败。
  2. Fulfilled(已成功):意味着操作成功完成。
  3. Rejected(已失败):意味着操作失败。

关键特性:不可逆性 状态只能从 Pending 变为 Fulfilled 或 Rejected。一旦状态改变,就不会再变,任何时候都可以得到这个结果


实例方法(用于处理结果)

  • .then(onFulfilled, onRejected): 注册成功或失败的回调。
  • .catch(onRejected): 专门用于捕捉错误,实际上是 .then(null, onRejected) 的语法糖。
  • .finally(onFinally): 无论成功还是失败都会执行,通常用于清理资源(如关闭 Loading 动画)

Promise 的构造函数体是同步执行的
当你 new Promise 的那一刻,执行器内部的代码会立即在当前主线程执行,并不会进入微任务队列

resolve()/reject() 本身是同步调用,但它们触发的 .then() / .catch() 回调是异步(微任务)


Promise.all

用于并行处理多个promise,可以同时执行多个异步操作(同时执行,而非顺序执行),并在所有操作完成后获取结果

参数,是多个promise实例

成功的条件是所有的promise成功,返回包含所有结果的数组,结果顺序固定,与传入的promise顺序一致

任何一个 Promise 失败,立即返回第一个失败的错误信息

Promise.all 适用于逻辑耦合的场景。如果任务 A 和任务 B 缺一不可才能渲染页面,就用它


Promise.allSettled()

并行处理多个promise,并在所有promise完成后返回包含每个promise结果的数组

Promise.allSettled 适用于任务相互独立且需要获取每个任务最终状态的情况。即使有些请求挂了,也不影响你展示其他成功的数据


Promise.race()

接受一个promise数组,返回第一个完成的promise,无论成功还是失败

特点: 典型的超时机制实现工具


Promise.any()

返回第一个成功的promise的值,全部失败的时候,返回错误信息

特点:高可用性。只有当所有人都不行了(AggregateError),它才放弃


手写Promise

底层的三个关键机制是:

  1. 状态机机制:pending -> fulfilled / rejected 的单向转换。
  2. 观察者模式:用数组存储成功/失败的回调,等待状态改变时触发。
  3. 微任务调度:确保 .then() 永远异步执行,保证执行顺序的预测性。

一次性、状态驱动的发布订阅者模式

第一步:订阅 (Subscription)

当你执行 promise.then(fn) 且此时状态为 PENDING 时,Promise 并没有执行 fn,而是把 fn 存入(push) 内部的数组中。

  • 这对应了发布订阅中的:注册/监听 (Listen/Subscribe)。

第二步:发布 (Publishing)

当异步操作结束,你调用 resolve(value)。此时,Promise 内部会遍历数组,把之前存入的所有 fn 拿出来依次执行。

  • 这对应了发布订阅中的:触发/广播 (Emit/Publish)。
// 定义三种状态
        const PENDING = 'pending';
        const FULFILLED = 'fulfilled';
        const REJECTED = 'rejected';

        class MyPromise {
            constructor(executor) {
                this.status = PENDING; // 初始状态
                this.value = undefined; // 成功的值
                this.reason = undefined; // 失败的原因

                // 成功和失败的回调队列(用于处理异步 PENDING 时的 then)
                this.onResolvedCallbacks = [];
                this.onRejectedCallbacks = [];

                // 内部的 resolve 工具函数
                const resolve = (value) => {
                    if (this.status === PENDING) {
                        this.status = FULFILLED;
                        this.value = value;
                        // 瞬间引爆:执行所有收集到的成功回调
                        this.onResolvedCallbacks.forEach(fn => fn());
                    }
                };

                // 内部的 reject 工具函数
                const reject = (reason) => {
                    if (this.status === PENDING) {
                        this.status = REJECTED;
                        this.reason = reason;
                        // 瞬间引爆:执行所有收集到的失败回调
                        this.onRejectedCallbacks.forEach(fn => fn());
                    }
                };

                // 对应你说的:try-catch 保证构造函数安全
                try {
                    executor(resolve, reject); 
                } catch (err) {
                    reject(err);
                }
            }

            then(onFulfilled, onRejected) {
                // 规范:参数可选,实现值穿透
                onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v;
                onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err };

                // 为了链式调用,必须返回一个新的 Promise 实例
                const promise2 = new MyPromise((resolve, reject) => {
                
                // 封装执行逻辑并放入微任务队列
                const fulfilledMicrotask = () => {
                    queueMicrotask(() => { // ⭐️ 关键:包装为异步微任务
                    try {
                        const x = onFulfilled(this.value);
                        // 递归解析 x 的值(处理 x 也是 Promise 的情况)
                        resolvePromise(promise2, x, resolve, reject);
                    } catch (e) {
                        reject(e);
                    }
                    });
                };

                const rejectedMicrotask = () => {
                    queueMicrotask(() => { // ⭐️ 关键:包装为异步微任务
                    try {
                        const x = onRejected(this.reason);
                        resolvePromise(promise2, x, resolve, reject);
                    } catch (e) {
                        reject(e);
                    }
                    });
                };

                // 状态机判断
                if (this.status === FULFILLED) {
                    fulfilledMicrotask();
                } else if (this.status === REJECTED) {
                    rejectedMicrotask();
                } else if (this.status === PENDING) {
                    // 对应你说的:异步时先 push 到队列
                    this.onResolvedCallbacks.push(fulfilledMicrotask);
                    this.onRejectedCallbacks.push(rejectedMicrotask);
                }
                });

                return promise2;
            }
        }


Async await

async/await 是 Promise 的语法糖,它的本质是 Promise + 生成器(Generator)的组合,让异步代码可以用同步的方式书写。

  • async 函数总是返回一个 Promise
  • await 等待一个 Promise 完成,并取出其结果
  • 本质上,await 后面的代码会被包装成 .then() 的回调

async 函数总是返回 Promise

// 情况1:返回普通值
async function fn1() {
    return 42;
}
// 等价于:return Promise.resolve(42)

// 情况2:返回 Promise
async function fn2() {
    return Promise.resolve(42);
}
// 直接返回这个 Promise(不会二次包装)

// 情况3:抛出错误
async function fn3() {
    throw new Error('出错了');
}
// 等价于:return Promise.reject(new Error('出错了'))

// 情况4:没有 return
async function fn4() {
    console.log('执行');
}
// 等价于:return Promise.resolve(undefined)

await 实际上是一个 “代码切割机”。当你写下 const res = await p1; 时,引擎会把你的函数从这里切成两半

  • 上半部分:同步执行,直到遇到 await。
  • 下半部分:被包装成一个微任务,丢进 .then() 的回调里,等待 p1 成功后再回来执行。

await 后面的代码被放进微任务队列的时机,只取决于 await 后面表达式的 Promise 何时被 resolve

await 后面的任何值,都会被 Promise.resolve() 包装成一个 Promise

场景resolve() 调用时机await 后面代码入队时机
Promise 内部全是同步代码立即立即入微任务
Promise 内部有 setTimeout(0ms)下一个宏任务下一个宏任务执行时才入微任务
Promise 内部有 100 层嵌套异步最内层 resolve 时最内层 resolve 时才入微任务
Promise 永远不 resolve永不永不入队,代码永远不执行

✅ await 只阻塞它后面的代码(在同一个 async 函数内)
✅ 把后面的代码包装成微任务后,立即让出主线程
✅ 继续执行 async 函数外面的同步代码和已有任务


用 Promise:当你需要并发处理时。比如 Promise.all([p1, p2])。

用 async/await:当你的异步任务有严格的先后顺序(串行)时。

底层原理:永远记住,await 下面的代码就是 .then() 里的微任务


assistant