根据Promises/A+规范手写promise

374 阅读10分钟

基本概念

Promise是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果,同时promise必须遵守Promises/A+规范。

基本术语

image-20231026223556446.png

  1. promise是带有then方法的对象或函数,其行为符合此规范。

  2. thenable是定义then方法的对象或函数。

  3. value是任何合法的JavaScript值(包括undefined、thenable或promise)。

  4. exception是使用throw语句抛出的值。

  5. reason是一个值,表示promise被拒绝的原因。

Promise状态(Promise States)

image-20231026223013324.png

  • 根据Promises/A+规范原文描述,promise的状态有且只有三种: pending(等待态)fulfilled(成功态)rejected(失败态)

    • pending态时,可以改变其状态为fulfilledrejected
    • 其中fulfilledrejected时状态是不可被改变的,这意味着状态是不可逆的。
  • promise处于pending态时,能够改变promise状态的方法有三种:

    1. 调用resolve把状态改为fulfilled;
    2. 调用reject把状态改为rejected;
    3. 抛出错误thow new Error(),把状态改为rejected;

直接冻手

  • 在代码中,会尽量给家人们标记好代码在Promises/A+的具体说明位置,方便结合规范食用。

第一步

  • 定义promise的三种状态
  • 定义成功原因value
  • 定义失败原因reason
  • promise在创建时,会接收一个functionexecutor作为参数,并且会默认执行一次此参数,同时返回内部定义的resolvereject方法供外部修改状态。
  • 实现一个then函数,在promise状态被修改时,需要调用对应的函数,接收两个参数:
    1. onFulfilled 成功后执行的回调。
    2. onRejected 失败后执行的回调。
const PENDING = "PENDING"; // 默认态
const FULFILLED = "FULFILLED"; // 成功态
const REJECTED = "REJECTED"; // 失败态

class Promise1 {
    constructor(executor) {
        this.state = PENDING; // 状态
        this.reason = null; // 失败原因
        this.value = null; // 成功数据
        const resolve = (value) => {
            // pending状态时,状态才可变更
            if (this.state === PENDING) {
                // 2.1.2.1 must not transition to any other state.
                this.state = FULFILLED;
                this.value = value; // 2.1.2.2 must have a value, which must not change.
            }
        };
        const reject = (reason) => {
            // pending状态时,状态才可变更
            if (this.state === PENDING) {
                // 2.1.3.1 must not transition to any other state.
                this.state = REJECTED;
                this.reason = reason; // 2.1.3.2 must have a reason, which must not change.
            }
        };
        // 能够改变promise状态的有resolve,reject和抛出异常throw new Error();
        try {
            executor(resolve, reject); // 默认会执行一次, 同时返回内部定义的resolve和reject方法供外部修改状态。
        } catch (error) {
            // 如果运行报错,会直接当作失败处理
            reject(error);
        }
    }
    then(onFulfilled, onRejected) {
        // 2.2 A promise must provide a then method to access its current or eventual value or reason.
        // A promise’s then method accepts two arguments: onFulfilled AND onRejected
        if (this.state === FULFILLED) {
            // 当前状态为成功态调用onFulfilled
            onFulfilled(this.value);
        } else if (this.state === REJECTED) {
            // 当前状态为失败态调用onRejected
            onRejected(this.reason);
        }
    }
}

  • 我们进行下简单的测试
const promise = new Promise1((resolve, reject) => {
    resolve("ok");
}).then((data) => {
    console.log(data); // ok
});
const promise = new Promise1((resolve, reject) => {
    reject("fail");
}).then(
    (data) => {},
    (error) => {
        console.log(error); // fail
    }
);
const promise = new Promise1((resolve, reject) => {
    resolve("ok");
    reject("fail"); // 这里将不被执行
}).then(
    (data) => {
        console.log(data); // ok
    },
    (error) => {
        console.log(error); // 不打印
    }
);

第二步

众所周知promise是解决回调地狱的,因此其使用场景的执行是异步的,第一步then被调用时,promise仍处于pending态,很显然是无法解决的。

要解决异步调用的问题,需要利用观察者模式,可以在另一篇文章中获得更详细的答案,这里就不详细赘述了。

  1. 定义onResolvedCallBacks缓存所有onFulfilled观察者函数;
  2. 定义onRejectedCallBacks缓存所有的onRejected观察者函数;
  3. then方法中增加判断pending逻辑;
  4. resolve被调用时,执行所有的onResolvedCallBacks
  5. reject被调用时,执行所有的onRejectedCallBacks
class Promise1 {
    constructor(executor) {
        // 省略....
        const resolve = (value) => {
            // pending状态时,状态才可变更
            if (this.state === PENDING) {
                // 2.1.2.1 must not transition to any other state.
                this.state = FULFILLED;
                this.value = value; // 2.1.2.2 must have a value, which must not change.
                this.onResolvedCallBacks.forEach((fn) => fn());
            }
        };
        const reject = (reason) => {
            // pending状态时,状态才可变更
            if (this.state === PENDING) {
                // 2.1.3.1 must not transition to any other state.
                this.state = REJECTED;
                this.reason = reason; // 2.1.3.2 must have a reason, which must not change.
                this.onRejectedCallBacks.forEach((fn) => fn());
            }
        };
        // 省略....
    }
    then(onFulfilled, onRejected) {
        // 2.2 A promise must provide a then method to access its current or eventual value or reason.
        // A promise’s then method accepts two arguments: onFulfilled AND onRejected
        if (this.state === FULFILLED) {
            // 省略....
        } else if (this.state === REJECTED) {
            // 省略....
        } else if (this.state === PENDING) {
            // 异步,状态为等待
            // 观察者模式,观察state变化,缓存onFulfilled和onRejected
            this.onResolvedCallBacks.push(() => {
                onFulfilled(this.value);
            });
            this.onRejectedCallBacks.push(() => {
                onRejected(this.reason);
            });
        }
    }
}

  • setTimeout模拟异步,再次测试
const promise = new Promise1((resolve, reject) => {
    setTimeout(() => {
        resolve("ok");
    }, 1000);
}).then((data) => {
    console.log(data); // ok
});
const promise = new Promise1((resolve, reject) => {
    setTimeout(() => {
        reject("fail");
    }, 1000);
}).then(
    (data) => {},
    (error) => {
        console.log(error); // fail
    }
);

第三步

最最最最重要的核心逻辑来了,“众所周知”它又来了,众所周知,promise是允许链式调用的。

image-20231026232609805.png

const promise = new Promise1((resolve, reject) => {
    setTimeout(() => {
        resolve("ok");
    }, 1000);
})
    .then((data) => {
        return data;
    })
    .then((data2) => {
        console.log(data2); // ok
    });

分析

文档2.2.7说到then must return a promise, then 必须返回一个promise,也正因为返回一个promise,我们才能够不断的调用其then方法。

那么疑问来了,完成第一、二步后,我们返回的都是一个静态的值value或者reason,需要返回一个promise,能不能直接返回this呢?

答案很显然是否定的,因为promise的状态一旦改变之后是不可逆的,如果返回当前的this状态不可逆将没有任何意义,所以我们需要返回一个new Promise。

class Promise1 {
    constructor(executor) {
        // 省略....
    }
    then(onFulfilled, onRejected) {
        if (this.state === FULFILLED) {
            // 省略....
        } else if (this.state === REJECTED) {
            // 省略....
        } else if (this.state === PENDING) {
            // 省略....
        }
        const promise2 = new Promise1((resolve, reject) => {});
        return promise2;
    }
}

把测试案例贴在这里对比,会比较明显看出问题。

const promise = new Promise1((resolve, reject) => {
    setTimeout(() => {
        resolve("ok");
    }, 1000);
}).then((data) => {
    return data; // a
});
promise.then((data2) => {
    // b
    console.log(data2); // undefined
});

确实已经返回了一个新的promise,并且可以调用then方法,但在b步骤中的data2是undefined,我们要如何将a步骤的数据传递给b呢?

显而易见,data就是第一个promiseonFulfilled运行结果,官方文档2.2.7.1也有相关叙述

image-20231030214628321.png

onFulfilledonRejected的运行结果为x,把then的运行逻辑丢进promise2里,将x返回给promise2即可。

class Promise1 {
    constructor(executor) {
        // 省略....
    }
    then(onFulfilled, onRejected) {
        const promise2 = new Promise1((resolve, reject) => {
            // 2.2 A promise must provide a then method to access its current or eventual value or reason.
            // A promise’s then method accepts two arguments: onFulfilled AND onRejected
            if (this.state === FULFILLED) {
                // 当前状态为成功态调用onFulfilled
                const x = onFulfilled(this.value);
                resolve(x);
            } else if (this.state === REJECTED) {
                // 当前状态为失败态调用onRejected
                const x = onRejected(this.reason);
                reject(x);
            } else if (this.state === PENDING) {
                // 异步,状态为等待
                // 观察者模式,观察state变化,缓存onFulfilled和onRejected
                this.onResolvedCallBacks.push(() => {
                    const x = onFulfilled(this.value);
                    resolve(x);
                });
                this.onRejectedCallBacks.push(() => {
                    const x = onRejected(this.reason);
                    reject(x);
                });
            }
        });
        return promise2;
    }
}

继续阅读2.2.7.1后半句run the Promise Resolution Procedure [[Resolve]](promise2, x),运行一个promise的解析函数,意思就是要把promise2onFulfilledonRejected的运行结果用一个处理函数进行处理,处理什么呢?当promise2onFulfilledonRejected运行结果有可能还是一个promise,因此需要进行处理,并且把promise2也以参数的形式传给处理函数。

再分析

  • promise2以参数的形式传给处理函数,按照上述情况很明显是获取不到promise2实例的,再看看官方文档3.1

image-20231030215553574.png

简而言之,通过“宏任务”机制或者“微任务”机制来确保这个异步执行,这里紧用setTimeout实现

function resolvePromise(x, promise2, resolve, reject) {}
class Promise1 {
    constructor(executor) {
        // 省略....
    }
    then(onFulfilled, onRejected) {
        const promise2 = new Promise1((resolve, reject) => {
            if (this.state === FULFILLED) {
                // 成功态调用
                // 获取fulfilled返回结果,根据返回结果判断是调用promise2的resolve还是reject
                setTimeout(() => {
                    try {
                        const x = onFulfilled(this.value);
                        resolvePromise(x, promise2, resolve, reject);
                    } catch (error) {
                        reject(error);
                    }
                }, 0);
            } else if (this.state === REJECTED) {
                // 失败态调用
                // 获取rejected返回结果,根据返回结果判断是调用promise2的resolve还是reject
                setTimeout(() => {
                    try {
                        const x = onRejected(this.reason);
                        resolvePromise(x, promise2, resolve, reject);
                    } catch (error) {
                        reject(error);
                    }
                }, 0);
            } else if (this.state === PENDING) {
                // 异步,状态为等待
                // 观察者模式,观察state变化,缓存onFulfilled和onRejected
                this.onResolvedCallBacks.push(() => {
                    setTimeout(() => {
                        try {
                            const x = onFulfilled(this.value);
                            resolvePromise(x, promise2, resolve, reject);
                        } catch (error) {
                            reject(error);
                        }
                    }, 0);
                });
                this.onRejectedCallBacks.push(() => {
                    setTimeout(() => {
                        try {
                            const x = onRejected(this.reason);
                            resolvePromise(x, promise2, resolve, reject);
                        } catch (error) {
                            reject(error);
                        }
                    }, 0);
                });
            }
        });
        return promise2;
    }
}
  • 接下来就剩下resolvePromise的实现了,可以在then方法中返回一个promise,看看文档2.3 The Promise Resolution Procedure,根据文档的判断逻辑,一步一步进行判断即可。

image-20231030220403472.png

function resolvePromise(x, promise2, resolve, reject) {
    // 2.3.1 If promise and x refer to the same object, reject promise with a TypeError as the reason.
    // 如果引用了自己,需要抛出一个错误。
    if (x === promise2) {
        return reject(new TypeError("循环引用promise"));
    }
    // 2.3.3.3 If both resolvePromise and rejectPromise are called, or multiple calls to the same argument are made, the first call takes precedence, and any further calls are ignored.
    let called = false;
    if ((typeof x === "object" && x !== null) || typeof x === "function") {
        // 2.3.3 Otherwise, if x is an object or function
        // 这种情况才有可能是promise
        // Let then be x.then
        try {
            let then = x.then; // 2.3.3.1 Let then be x.then
            if (typeof then === "function") {
                // 2.3.3.3 If then is a function, call it with x as this, first argument resolvePromise, and second argument 				  rejectPromise, where
                then.call(
                    x,
                    (y) => {
                        // 2.3.3.3.1 If/when resolvePromise is called with a value y, run [[Resolve]](promise, y).
                        if (called) return;
                        called = true;
                        resolvePromise(y, promise2, resolve, reject);
                    },
                    (r) => {
                        // 2.3.3.3.2 If/when rejectPromise is called with a reason r, reject promise with r.
                        if (called) return;
                        called = true;
                        reject(r);
                    }
                );
            } else {
                // 2.3.3.4 If x is not an object or function, fulfill promise with x
                // x是普通值, 直接返回
                resolve(x);
            }
        } catch (error) {
            // 2.3.3.2 If retrieving the property x.then results in a thrown exception e, reject promise with e as the reason.
            if (called) return;
            called = true;
            reject(error);
        }
    } else {
        // 2.3.3.4 If x is not an object or function, fulfill promise with x
        // x是普通值, 直接返回
        resolve(x);
    }
}

完整代码

const PENDING = "PENDING"; // 默认态
const FULFILLED = "FULFILLED"; // 成功态
const REJECTED = "REJECTED"; // 失败态

function resolvePromise(x, promise2, resolve, reject) {
    // 2.3.1 If promise and x refer to the same object, reject promise with a TypeError as the reason.
    // 如果引用了自己,需要抛出一个错误。
    if (x === promise2) {
        return reject(new TypeError("循环引用promise"));
    }
    // 2.3.3.3 If both resolvePromise and rejectPromise are called, or multiple calls to the same argument are made, the first call takes precedence, and any further calls are ignored.
    let called = false;
    if ((typeof x === "object" && x !== null) || typeof x === "function") {
        // 2.3.3 Otherwise, if x is an object or function
        // 这种情况才有可能是promise
        // Let then be x.then
        try {
            let then = x.then; // 2.3.3.1 Let then be x.then
            if (typeof then === "function") {
                // 2.3.3.3 If then is a function, call it with x as this, first argument resolvePromise, and second argument 				  rejectPromise, where
                then.call(
                    x,
                    (y) => {
                        // 2.3.3.3.1 If/when resolvePromise is called with a value y, run [[Resolve]](promise, y).
                        if (called) return;
                        called = true;
                        resolvePromise(y, promise2, resolve, reject);
                    },
                    (r) => {
                        // 2.3.3.3.2 If/when rejectPromise is called with a reason r, reject promise with r.
                        if (called) return;
                        called = true;
                        reject(r);
                    }
                );
            } else {
                // 2.3.3.4 If x is not an object or function, fulfill promise with x
                // x是普通值, 直接返回
                resolve(x);
            }
        } catch (error) {
            // 2.3.3.2 If retrieving the property x.then results in a thrown exception e, reject promise with e as the reason.
            if (called) return;
            called = true;
            reject(error);
        }
    } else {
        // 2.3.3.4 If x is not an object or function, fulfill promise with x
        // x是普通值, 直接返回
        resolve(x);
    }
}

class Promise1 {
    constructor(executor) {
        this.state = PENDING; // 状态
        this.reason = null; // 失败原因
        this.value = null; // 成功数据
        this.onResolvedCallBacks = []; // 成功态的回调缓存
        this.onRejectedCallBacks = []; // 失败态的回调缓存
        const resolve = (value) => {
            // pending状态时,状态才可变更
            if (this.state === PENDING) {
                // 2.1.2.1 must not transition to any other state.
                this.state = FULFILLED;
                this.value = value; // 2.1.2.2 must have a value, which must not change.
                this.onResolvedCallBacks.forEach((fn) => fn());
            }
        };
        const reject = (reason) => {
            // pending状态时,状态才可变更
            if (this.state === PENDING) {
                // 2.1.3.1 must not transition to any other state.
                this.state = REJECTED;
                this.reason = reason; // 2.1.3.2 must have a reason, which must not change.
                this.onRejectedCallBacks.forEach((fn) => fn());
            }
        };
        // 能够改变promise状态的有resolve,reject和抛出异常throw new Error();
        try {
            executor(resolve, reject); // 默认会执行
        } catch (error) {
            reject(error);
        }
    }
    then(onFulfilled, onRejected) {
        // 值的穿透
        // 2.2.1.1 If onFulfilled is not a function, it must be ignored.
        // 2.2.1.2 If onRejected is not a function, it must be ignored.
        onFulfilled =
            typeof onFulfilled === "function" ? onFulfilled : (v) => v;
        onRejected =
            typeof onRejected === "function"
                ? onRejected
                : (e) => {
                      throw e;
                  };
        const promise2 = new Promise1((resolve, reject) => {
            if (this.state === FULFILLED) {
                // 成功态调用
                // 获取fulfilled返回结果,根据返回结果判断是调用promise2的resolve还是reject
                setTimeout(() => {
                    try {
                        const x = onFulfilled(this.value);
                        resolvePromise(x, promise2, resolve, reject);
                    } catch (error) {
                        reject(error);
                    }
                }, 0);
            } else if (this.state === REJECTED) {
                // 失败态调用
                // 获取rejected返回结果,根据返回结果判断是调用promise2的resolve还是reject
                setTimeout(() => {
                    try {
                        const x = onRejected(this.reason);
                        resolvePromise(x, promise2, resolve, reject);
                    } catch (error) {
                        reject(error);
                    }
                }, 0);
            } else if (this.state === PENDING) {
                // 异步,状态为等待
                // 观察者模式,观察state变化,缓存onFulfilled和onRejected
                this.onResolvedCallBacks.push(() => {
                    setTimeout(() => {
                        try {
                            const x = onFulfilled(this.value);
                            resolvePromise(x, promise2, resolve, reject);
                        } catch (error) {
                            reject(error);
                        }
                    }, 0);
                });
                this.onRejectedCallBacks.push(() => {
                    setTimeout(() => {
                        try {
                            const x = onRejected(this.reason);
                            resolvePromise(x, promise2, resolve, reject);
                        } catch (error) {
                            reject(error);
                        }
                    }, 0);
                });
            }
        });
        return promise2;
    }
}