深入浅出现代 JavaScript—手写 Promise(2)

246 阅读2分钟

「这是我参与11月更文挑战的第16天,活动详情查看:2021最后一次更文挑战

上一次简单实现了 Promise,在开始今天新的内容简单回顾一下,创建一个类 MyPromise 在构造函数接受一个函数类型参数 executor 在构造函数直接调用这个函数将其执行是,构造函数中初始化 3 个属性,分别是 Promise 的状态,以及 value 和 reason 表示 Promise 在履行 Promise 返回的值以及拒绝履行 Promise 给出理由(reason) ,接下来定义两个函数分别是resolvereject 作为 executor 参数。然后就是 then 方法,在 then 方法用户可以通过回调函数使用成功或者失败返回的值,并对其进行处理。

class MyPromise {
    constructor(executor) {
        this.state = "pending";
        this.value = undefined;
        this.reason = undefined;

        let resolve = value => {
            if (this.state === 'pending') {
                this.state = 'fullFilled';
                this.value = value;
            }
        }

        let reject = reason => {
            if (this.state === 'pending') {
                this.state = 'rejected';
                this.reason = reason;
            }
        }

        executor(resolve, reject);
    }

    then(onFullFilled, onRejected) {
        if (this.state === 'fullFilled') {
            onFullFilled(this.value)
        }
        if (this.state === 'rejected') {
            onRejected(this.reason);
        }
    }
}

new Promise((resolve, reject) => {
    console.log("hello promise...");
    // resolve("resolve");
    reject("reject...");
}).then((value) => console.log(value), (reason) => console.log(reason));

如果我们把 resolve 切换到 reject 这样一来就会输出 reject... 如果既有 resolve 又有 reject 那么谁在前就执行谁,而且状态从 pending 转换到 resolve 或者 reject 只能进行一次。

new MyPromise((resolve, reject) => {
    console.log("hello promise...");
    resolve("resolve");
    reject("reject...");
}).then((value) => console.log(value), (reason) => console.log(reason));

捕获错误

new Promise((resolve, reject) => {
    throw new Error("error...");
    console.log("hello promise...");
    resolve("resolve");
    // reject("reject...");
}).then((value) => console.log(value), (reason) => console.log(reason));

如果在 js 原生 Promise 中抛出一个错误,错误是可以捕获。

Error: error...
    at (index):49
    at new Promise (<anonymous>)
    at (index):48
Live reload enabled.
try {
    executor(resolve, reject);
} catch (error) {
    reject(error);
}

异步

new MyPromise((resolve, reject) => {
    // throw new Error("error...");
    console.log("hello promise...");
    setTimeout(function(){
        resolve("resolve");
    },0)
    // reject("reject...");
}).then((value) => console.log(value), (reason) => console.log(reason));

如果将 reslve("resolve") 放入了 setTimeout 这里去执行,这部分代码就不会去被执行,因为当执行到 setTimeout 是否会调用浏览器的 API setTimeout 将内部回调函数(任务)事件队列,然后等待当前执行栈中所有同步代码都执行完,才会从事件队列中调取 resolve("resolve") 去执行,所以当执行回调函数(value) => console.log(value)

if (this.state === 'fullFilled') {
    onFullFilled(this.value)
}

此时状态还没有从pending变为fullFilled,所以无法输出resolve。要解决这个问题就需要用到发布订阅设计模式。首先我们创建两个数组用于缓存在状态是 pending 时候将成功和失败的回调函数都缓存起来。

this.onResolvedCallbacks = [];
this.onRejectedCallbacks = [];

然后在执行注册回调函数then方法时候,判断当状态为pending 时候将回调函数缓存起来。

if(this.state === 'pending'){
    this.onResolvedCallbacks.push(() => { onFullFilled(this.value) });
    this.onRejectedCallbacks.push(() => { onRejected(this.reason) });
}

然后重写一下 resolvereject 方法,这样就实现订阅发布模式来实现将回调函数存储起来直到状态发生改变才去执行缓存起来的函数。

let resolve = value => {
    if (this.state === 'pending') {
        this.state = 'fullFilled';
        this.value = value;
        this.onResolvedCallbacks.forEach(fn=>fn())
    }
}

let reject = reason => {
    if (this.state === 'pending') {
        this.state = 'rejected';
        this.reason = reason;
        this.onRejectedCallbacks.forEach(fn=>fn())
    }
}