JS炼化:手写一下promise——用一份外卖,看懂状态机+两个回调篓子

0 阅读13分钟

用一份外卖,看懂状态机+两个回调篓子

不少初学者看到完整版Promise手写源码就犯难,繁杂的边界处理和进阶优化让人望而生畏。其实抛开这些锦上添花的拓展逻辑,Promise的核心本质特别简单:一个状态机 + 两个回调篓子,所有功能都由此衍生。手写Promise从来不是盲目造轮子,核心目的就是拆开原生API的黑盒,告别只会调用不懂原理的陌生感,彻底吃透异步运行的底层逻辑。

大家或许对源码逻辑感到晦涩,但一定熟悉点外卖的日常。接下来我就用点外卖的生活化比喻,带你轻松弄懂状态机和回调篓子的核心运作逻辑。

先看逻辑思路

你点了一份外卖 = 发起一个异步任务

1. 状态机 = 外卖订单状态
  •  pending :商家正在做饭(任务进行中)
  •  fulfilled :外卖送到你手上(成功)
  •  rejected :商家没货/取消订单(失败)

状态机干了啥?

  • 告诉你现在能不能吃
  • 保证只会送一次,不会反复送
  • 饭做好了就一直是做好的状态,不会变回“正在做”
  • 结果会永久保存,你什么时候拿都有

(状态机 = 给异步任务定规矩: 只能走一次,走到哪就是哪,结果永久留着。)

2.回调篓子 = 你给外卖员留的“送达通知方式”

你还没拿到外卖时( pending ),你跟外卖员说:

  • 送到了给我打电话
  • 送不成给我发短信

这些“打电话、发短信”,就是你存在  then  里的回调。

回调篓子干了啥?

  • 饭还没好,先把你的要求存起来
  • 不催、不闹、不嵌套
  • 等饭一好,一次性按顺序执行

(回调篓子 = 暂存你的“后续操作”, 异步没跑完,先排队等通知。)

3. 两者合在一起,才是 Promise

流程是这样的:

1. 你下单 → Promise 新建

2. 状态立刻变成  pending → 商家正在做饭

3. 你调用  .then()  留下回调 → 把“打电话/发短信”放进篓子存好

4.饭做好了 →  resolve()

  • 状态变成  fulfilled 
  • 拿出成功篓子里的所有回调,挨个执行 → 挨个打电话通知你

5.饭做不成 →  reject()

  • 状态变成  rejected 
  • 拿出失败篓子里的回调,挨个执行 → 发短信告诉你取消了

再到后面链式调用“骑手”干了什么,“平台”有哪些补救措施

(今天我们就以点外卖的方式: 从0 开始,一小块一小块叠代码,先懂原理再落地实现,彻底撕开 Promise 的黑盒!)

 

第一阶段:逐块拆解手写「纯状态机基础版Promise」

比喻以注释的形式写进代码里方便理解

下单必有初始状态,Promise 创建瞬间也必须固定 pending 态,状态只能单向流转,这是一切的根基。我们从零一块块码

第1块:定义三大核心状态常量(杜绝硬编码写错)

// 对标外卖三种固定状态:做饭中 / 已送达 / 已取消
const STATUS_PENDING = "pending";    // 等待中-正在做饭
const STATUS_FULFILLED = "fulfilled";// 成功-外卖送到
const STATUS_REJECTED = "rejected";  // 失败-订单取消

为什么单独定义常量? 统一管理状态名,全程复用,避免手写字符串拼写错误。(可以理解为有报错提示,同时也能让大家看代码更清晰的行业规范)

第2块:搭建Promise空类骨架(相当于开通下单通道)

// 创建自定义Promise类,开始搭建订单系统外壳
class _Promise {
    // 构造函数:实例化瞬间触发 = 用户点击下单
    constructor(executor = () => {}) {

    }
}

executor 就是商家做饭的核心流程,默认给空函数防止报错。

第3块:构造器初始化核心属性(订单基础信息登记)

const STATUS_PENDING = "pending";
const STATUS_FULFILLED = "fulfilled";
const STATUS_REJECTED = "rejected";

class _Promise {
    constructor(executor = () => {}) {
        // 刚下单默认状态:商家正在做饭 pending
        this.status = STATUS_PENDING;
        // 预留位置:存放送到手的外卖成果(成功结果)
        this.value = undefined;
        // 预留位置:存放订单失败的原因(商家没货/超时)
        this.reason = undefined;
    }
}

核心规则:只要Promise一创建,天生固定 pending,绝不允许开局直接成功/失败。

第4块:内部定义resolve函数(外卖送达开关)

const STATUS_PENDING = "pending";
const STATUS_FULFILLED = "fulfilled";
const STATUS_REJECTED = "rejected";

class _Promise {
    constructor(executor = () => {}) {
        this.status = STATUS_PENDING;
        this.value = undefined;
        this.reason = undefined;

        // 专属开关:调用就代表外卖顺利送达
        const resolve = (value) => {
            // 铁律校验:只有还在做饭中,才能改成已送达
            if (this.status === STATUS_PENDING) {
                this.status = STATUS_FULFILLED;
                // 把到手的外卖存起来
                this.value = value;
            }
        };
    }
}

状态不可逆核心体现:已经送达/取消的订单,再也不能二次修改状态。

第5块:内部追加reject函数(订单取消开关)

const STATUS_PENDING = "pending";
const STATUS_FULFILLED = "fulfilled";
const STATUS_REJECTED = "rejected";

class _Promise {
    constructor(executor = () => {}) {
        this.status = STATUS_PENDING;
        this.value = undefined;
        this.reason = undefined;

        const resolve = (value) => {
            if (this.status === STATUS_PENDING) {
                this.status = STATUS_FULFILLED;
                this.value = value;
            }
        };

        // 专属开关:调用就代表订单作废取消
        const reject = (reason) => {
            // 同样铁律:只有做饭中才能取消订单
            if (this.status === STATUS_PENDING) {
                this.status = STATUS_REJECTED;
                // 把取消原因记录下来
                this.reason = reason;
            }
        };
    }
}

第6块:立即执行executor做饭流程(下单立刻开工)

const STATUS_PENDING = "pending";
const STATUS_FULFILLED = "fulfilled";
const STATUS_REJECTED = "rejected";

class _Promise {
    constructor(executor = () => {}) {
        this.status = STATUS_PENDING;
        this.value = undefined;
        this.reason = undefined;

        const resolve = (value) => {
            if (this.status === STATUS_PENDING) {
                this.status = STATUS_FULFILLED;
                this.value = value;
            }
        };

        const reject = (reason) => {
            if (this.status === STATUS_PENDING) {
                this.status = STATUS_REJECTED;
                this.reason = reason;
            }
        };

        // 下单瞬间直接开火做饭!把两个开关交给外部掌控
        executor(resolve, reject);
    }
}

关键特性:Promise构造器里的执行器是同步立即执行,不会等待延迟。

第7块:追加基础then方法(外卖到了要干啥)

const STATUS_PENDING = "pending";
const STATUS_FULFILLED = "fulfilled";
const STATUS_REJECTED = "rejected";

class _Promise {
    constructor(executor = () => {}) {
        this.status = STATUS_PENDING;
        this.value = undefined;
        this.reason = undefined;

        const resolve = (value) => {
            if (this.status === STATUS_PENDING) {
                this.status = STATUS_FULFILLED;
                this.value = value;
            }
        };

        const reject = (reason) => {
            if (this.status === STATUS_PENDING) {
                this.status = STATUS_REJECTED;
                this.reason = reason;
            }
        };

        executor(resolve, reject);
    }

    // 定制收货操作:成功干啥、失败干啥
    then(onFulfilled, onRejected) {
        // 外卖已经送到,有成功回调就直接执行
        if (this.status === STATUS_FULFILLED && typeof onFulfilled === 'function') {
            onFulfilled(this.value);//这个onFulfilled传进来必须是函数,加判断为了防止报错崩程序
        }
        // 订单已经取消,有失败回调就直接执行
        if (this.status === STATUS_REJECTED && typeof onRejected === 'function') {
            onRejected(this.reason);//这里同理
        }
        // 注意:当前版本暂时处理不了还在做饭中的异步等待场景
    }
}

第一阶段整合完整版(逐块拼装最终成品)

// 1. 定义外卖三大状态常量
const STATUS_PENDING = "pending";
const STATUS_FULFILLED = "fulfilled";
const STATUS_REJECTED = "rejected";

// 2. 自定义基础Promise类
class _Promise {
    constructor(executor = () => {}) {
        // 3. 初始化订单默认状态和存储容器
        this.status = STATUS_PENDING;
        this.value = undefined;
        this.reason = undefined;

        // 4. 送达开关逻辑
        const resolve = (value) => {
            if (this.status === STATUS_PENDING) {
                this.status = STATUS_FULFILLED;
                this.value = value;
            }
        };

        // 5. 取消开关逻辑
        const reject = (reason) => {
            if (this.status === STATUS_PENDING) {
                this.status = STATUS_REJECTED;
                this.reason = reason;
            }
        };

        // 6. 立刻启动做饭流程
        executor(resolve, reject);
    }

    // 7. 收货处理then方法
    then(onFulfilled, onRejected) {
        if (this.status === STATUS_FULFILLED && typeof onFulfilled === 'function') {
            onFulfilled(this.value);
        }
        if (this.status === STATUS_REJECTED && typeof onRejected === 'function') {
            onRejected(this.reason);
        }
    }
}

下一阶段我们就给这套基础状态机装上「回调篓子队列」,解决异步等待存任务的问题,完美适配真实延时外卖场景~

第二阶段:加装「回调篓子」完整版(衔接基础状态机,递进改造)

上一版只有状态机,处理不了异步延时——就像外卖要等一会才送到,你提前说了收货要做的事,总得先记下来,这两个数组  resolveQueue/rejectQueue  就是专门存事的「回调篓子」。

第2块:类骨架不变,构造器里新增两个回调篓子属性

const STATUS_PENDING = "pending";
const STATUS_FULFILLED = "fulfilled";
const STATUS_REJECTED = "rejected";

class _Promise {
    constructor(executor = () => {}) {
        // 原有基础状态不变
        this.status = STATUS_PENDING;
        this.value = undefined;
        this.reason = undefined;

        // ========== 全新加装:两个回调篓子 ==========
        // 成功篓子:存外卖没送到时,所有收货要做的事
        this.resolveQueue = [];
        // 失败篓子:存订单没取消前,所有失败兜底要做的事
        this.rejectQueue = [];
    }
}

改动说明:凭空新增两个数组,专门排队存待执行的回调函数,对应「先记下来,等送达再办」。

第3块:改造 resolve 函数——送达后自动清空执行成功篓子

const STATUS_PENDING = "pending";
const STATUS_FULFILLED = "fulfilled";
const STATUS_REJECTED = "rejected";

class _Promise {
    constructor(executor = () => {}) {
        this.status = STATUS_PENDING;
        this.value = undefined;
        this.reason = undefined;
        this.resolveQueue = [];
        this.rejectQueue = [];

        const resolve = (value) => {
            if (this.status === STATUS_PENDING) {
                this.status = STATUS_FULFILLED;
                this.value = value;
                // ========== 新增逻辑:外卖送到,挨个执行篓子里所有寄存的事 ==========
                this.resolveQueue.forEach(fn => fn(this.value));
            }
        };
    }
}

第4块:同步改造 reject 函数——订单取消清空执行失败篓子

const STATUS_PENDING = "pending";
const STATUS_FULFILLED = "fulfilled";
const STATUS_REJECTED = "rejected";

class _Promise {
    constructor(executor = () => {}) {
        this.status = STATUS_PENDING;
        this.value = undefined;
        this.reason = undefined;
        this.resolveQueue = [];
        this.rejectQueue = [];

        const resolve = (value) => {
            if (this.status === STATUS_PENDING) {
                this.status = STATUS_FULFILLED;
                this.value = value;
                this.resolveQueue.forEach(fn => fn(this.value));
            }
        };

        const reject = (reason) => {
            if (this.status === STATUS_PENDING) {
                this.status = STATUS_REJECTED;
                this.reason = reason;
                // ========== 新增逻辑:订单取消,挨个执行失败篓子里寄存的事 ==========
                this.rejectQueue.forEach(fn => fn(this.reason));
            }
        };
    }
}

第6块:核心改造 then 方法——判断 pending,把回调塞进篓子

这是最关键改动:外卖还在做(pending),不执行,直接把事装篓子里排队

const STATUS_PENDING = "pending";
const STATUS_FULFILLED = "fulfilled";
const STATUS_REJECTED = "rejected";

class _Promise {
    constructor(executor = () => {}) {
        this.status = STATUS_PENDING;
        this.value = undefined;
        this.reason = undefined;
        this.resolveQueue = [];
        this.rejectQueue = [];

        const resolve = (value) => {
            if (this.status === STATUS_PENDING) {
                this.status = STATUS_FULFILLED;
                this.value = value;
                this.resolveQueue.forEach(fn => fn(this.value));
            }
        };

        const reject = reason => {
            if (this.status === STATUS_PENDING) {
                this.status = STATUS_REJECTED;
                this.reason = reason;
                this.rejectQueue.forEach(fn => fn(this.reason));
            }
        };

        executor(resolve, reject);
    }

    then(onFulfilled, onRejected) {
        // 情况1:已经送到了,直接办收货的事
        if (this.status === STATUS_FULFILLED ) {
            onFulfilled(this.value);
        }
        // 情况2:已经取消了,直接办兜底的事
        else if (this.status === STATUS_REJECTED ) {
            onRejected(this.reason);
        }
        // ========== 全新核心逻辑:还在做饭 pending → 塞进对应篓子存起来 ==========
        else if (this.status === STATUS_PENDING) {
           this.resolveQueue.push(onFulfilled);
           this.rejectQueue.push(onRejected);
        }
    }
}

加装回调篓子·阶段完整汇总代码

// 外卖三态常量
const STATUS_PENDING = "pending";
const STATUS_FULFILLED = "fulfilled";
const STATUS_REJECTED = "rejected";

class _Promise {
    constructor(executor = () => {}) {
        // 基础状态机属性
        this.status = STATUS_PENDING;
        this.value = undefined;
        this.reason = undefined;

        // 两个回调篓子队列
        this.resolveQueue = [];
        this.rejectQueue = [];

        // 送达开关 + 执行成功队列
        const resolve = (value) => {
            if (this.status === STATUS_PENDING) {
                this.status = STATUS_FULFILLED;
                this.value = value;
                this.resolveQueue.forEach(fn => fn(this.value));
            }
        };

        // 取消开关 + 执行失败队列
        const reject = (reason) => {
            if (this.status === STATUS_PENDING) {
                this.status = STATUS_REJECTED;
                this.reason = reason;
                this.rejectQueue.forEach(fn => fn(this.reason));
            }
        };

        // 立刻执行做饭流程
        executor(resolve, reject);
    }

    // 智能分发:已完成直接执行,pending就装篓子
    then(onFulfilled, onRejected) {
        if (this.status === STATUS_FULFILLED ) {
            onFulfilled(this.value);
        } else if (this.status === STATUS_REJECTED ) {
            onRejected(this.reason);
        } else if (this.status === STATUS_PENDING) {
             this.resolveQueue.push(onFulfilled);
             this.rejectQueue.push(onRejected);
        }
    }
}

第三阶段:刚需进阶版 基础链式调用(无边界裸奔版,核心骨架)

加装核心链式调用真正解决回调函数嵌套地狱

const STATUS_PENDING = "pending";
const STATUS_FULFILLED = "fulfilled";
const STATUS_REJECTED = "rejected";

class _Promise {
    constructor(executor = () => {}) {
        this.status = STATUS_PENDING;
        this.value = undefined;
        this.reason = undefined;
        this.rejectReason = undefined;
        this.resolveQueue = [];
        this.rejectQueue = [];

        const resolve = (value) => {
            if (this.status === STATUS_PENDING) {
                this.status = STATUS_FULFILLED;
                this.value = value;
                this.resolveQueue.forEach(fn => fn(this.value));
            }
        };

        const reject = (reason) => {
            if (this.status === STATUS_PENDING) {
                this.status = STATUS_REJECTED;
                this.reason = reason;
                this.rejectQueue.forEach(fn => fn(this.reason));
            }
        };

        executor(resolve, reject);
    }

    then(onFulfilled, onRejected) {
        // 核心刚需:返回新实例,实现链式接龙
        return new _Promise((nextResolve, nextReject) => {
            // 封装统一骑手任务:复用逻辑、可立即执行可入篓寄存、中转链式值
             // 封装统一处理函数handleSuccess原因:
            // 1.逻辑抽离复用,不用多处重复写判断/执行逻辑
            // 2.包装成独立函数,既能立即执行,也可直接塞进队列寄存
            // 3.中转结果交给下一个Promise,支撑链式流转
            const handleSuccess = () => {
                const res = onFulfilled(this.value);
                nextResolve(res);
            };
               // 同成功处理逻辑:统一封装复用、可入队列、中转链式结果
            const handleFail = () => {
                const err = onRejected(this.reason);
                nextResolve(err);
            };

            if (this.status === STATUS_FULFILLED) {
                handleSuccess();
            } else if (this.status === STATUS_REJECTED) {
                handleFail();
            } else if (this.status === STATUS_PENDING) {
                this.resolveQueue.push(handleSuccess);
                this.rejectQueue.push(handleFail);
            }
        });
    }
}

刚需裸奔版存在核心问题(对应骑手配送漏洞)

1. 执行器executor报错直接崩程序(后厨做饭出事直接瘫痪,无应急)

2. then乱传非函数、空传省略回调直接报错(招了不会干活的假骑手,配送直接翻车)

3. 无值透传,中间空then直接断链式(中途骑手离岗,外卖没人接力送,链路废掉)   4. then内部回调自己报错无捕获(骑手送餐中途出事,全程没人兜底救援)

分步针对性边界优化(只改对应位置)

优化1:构造器加try/catch 兜底executor全局报错

只改constructor最后一行执行代码,其余全不变:

// 原有执行代码删掉,替换成下面
try {
  executor(resolve, reject); // 正常执行商家出餐
} catch (err) {
  reject(err); // 改动注释:后厨做饭报错直接转订单失败兜底,不崩系统
}

  优化2:加函数校验+值透传 解决乱传/空传骑手断链问题

只改then里handle核心逻辑+队列存入判断:

const handleSuccess = () => {
  // 改动注释:判断是不是正经骑手函数,不是就原值透传接力,不废单
  const res = typeof onFulfilled === 'function' ? onFulfilled(this.value) : this.value;
  nextResolve(res);
};
const handleFail = () => {
  // 改动注释:失败回调同样校验+原因透传,保证坏单也能顺畅接力
  const err = typeof onRejected === 'function' ? onRejected(this.reason) : this.reason;
  nextResolve(err);
};

// 底部pending入队也改一行
if (this.status === STATUS_PENDING) {
  // 改动注释:只把正经骑手存进任务篓,假骑手直接拒收不占用队列
  typeof onFulfilled === 'function' && this.resolveQueue.push(handleSuccess);
  typeof onRejected === 'function' && this.rejectQueue.push(handleFail);
}
 

优化3:内层try/catch 兜底骑手送餐中途自身报错(最终闭环)

只再包一层内部捕获:

const handleSuccess = () => {
  // 改动注释:骑手干活中途出错即时救援,报错直接切失败单流转
  try {
    const res = typeof onFulfilled === 'function' ? onFulfilled(this.value) : this.value;
    nextResolve(res);
  } catch (err) {
    nextReject(err);
  }
};
const handleFail = () => {
  try {
    const err = typeof onRejected === 'function' ? onRejected(this.reason) : this.reason;
    nextResolve(err);
  } catch (err) { // 改动注释:失败处理出错同样兜底捕获,全链路无死角
    nextReject(err);
  }
};

 

最终完整完善版(直接阅读注释说明看到整个流程)

// 外卖订单三大固定状态:待接单/配送完成/订单拒收取消
const STATUS_PENDING = "pending";
const STATUS_FULFILLED = "fulfilled";
const STATUS_REJECTED = "rejected";

class _Promise {
    constructor(executor = () => {}) {
        // 初始化订单默认状态:全部处于待接单
        this.status = STATUS_PENDING;
        // 存放配送完成的餐品结果
        this.value = undefined;
        // 存放订单拒收的原因备注
        this.reason = undefined;

        // 骑手任务收纳篓:排队等候的配送任务/拒收善后任务
        this.resolveQueue = [];
        this.rejectQueue = [];

        // 配送放行开关:只有待接单能改成完成,批量执行所有等候配送骑手
        const resolve = (value) => {
            if (this.status === STATUS_PENDING) {
                this.status = STATUS_FULFILLED;
                this.value = value;
                this.resolveQueue.forEach(fn => fn(this.value));
            }
        };

        // 订单拒收开关:只有待接单能改成取消,批量执行善后骑手任务
        const reject = (reason) => {
            if (this.status === STATUS_PENDING) {
                this.status = STATUS_REJECTED;
                this.reason = reason;
                this.rejectQueue.forEach(fn => fn(this.reason));
            }
        };

        // 后厨出餐容错:做饭翻车直接判订单失败,不瘫痪整个店铺
        try {
            executor(resolve, reject);
        } catch (err) {
            reject(err);
        }
    }

    then(onFulfilled, onRejected) {
        // 链式核心:每接一单就生成全新订单,实现骑手接力直送,完成异步时间扁平化
        return new _Promise((nextResolve, nextReject) => {
            // 统一封装正规配送骑手任务
            const handleSuccess = () => {
                // 骑手上岗核验+原值代送透传+送餐中途意外兜底
                try {
                    const res = typeof onFulfilled === 'function' ? onFulfilled(this.value) : this.value;
                    nextResolve(res);
                } catch (err) {
                    nextReject(err);
                }
            };
            // 统一封装订单善后退款骑手任务
            const handleFail = () => {
                // 坏单兜底核验+原因透传+善后出错应急保护
                try {
                    const err = typeof onRejected === 'function' ? onRejected(this.reason) : this.reason;
                    nextResolve(err);
                } catch (err) {
                    nextReject(err);
                }
            };

            // 根据订单当前状态调度骑手
            if (this.status === STATUS_FULFILLED) {
                handleSuccess(); // 已出餐直接派送
            } else if (this.status === STATUS_REJECTED) {
                handleFail(); // 已拒收直接善后
            } else if (this.status === STATUS_PENDING) {
                // 只收正经上岗骑手进任务篓,杂牌无效骑手直接拒收
                typeof onFulfilled === 'function' && this.resolveQueue.push(handleSuccess);
                typeof onRejected === 'function' && this.rejectQueue.push(handleFail);
            }
        });
    }
}

整个流程结尾

我们全程用外卖订单+骑手配送的思路走完了手写Promise的主要核心流程:

最初先搭建核心骨架,定好订单三态状态机,搭配存放任务的回调篓,实现了最基础的异步任务寄存能力;

接着核心打通链式调用逻辑,每调用一次then就生成一张全新外卖订单,让骑手接力顺路配送,把嵌套混乱的回调地狱改成线性直行的流程,完成了Promise最关键的异步扁平化;

最后一步步叠加全套边界优化,用try/catch兜底后厨出餐报错、骑手送餐中途异常,用函数筛查过滤不会干活的假骑手,搭配值透传规则让空岗骑手原样代送不丢单,保障整条配送链路永远顺畅不崩。

至此我们就从最简裸奔版,迭代打磨出了逻辑完整、健壮可用的完整版基础Promise。至于 Promise.all 、 Promise.race 这类静态工具方法,只是在这套核心完备的订单系统之上,额外封装的组合派单简易逻辑,属于锦上添花的拓展用法,不改动我们底层状态机、队列调度、链式流转的核心架构,这里就不再额外展开赘述了。

如有理解不当,欢迎大家指正,一起学习进步