乐趣区

手写Promise-实现一个基础的Promise

前端开发中常常会用到 Promise,不过有局部人并不分明 Promise 的原理,本文也是自己在学习 Promise 时对 Promis 的一些意识,心愿能对各位童鞋有所帮忙。

手写 Promise – 实现一个根底的 Promise
[手写 Promise – 实例办法 catch、finally]()
[手写 Promise – 罕用静态方法 all、any、resolve、reject、race]()

从意识 Promise 开始。。。

/* 模仿一个简略的异步行为 */
function fetchData() {return new Promise((resolve, reject) => {setTimeout(() => {resolve('willem');
        }, 1000);
    });
}

fetchData().then((data) => {
    // after 1000ms
    console.log(data); // willem
    return 'wei';
}, (err) => {}).then((data2) => {console.log(data2); // wei
});

下面的例子算是一个最常见的用法,可能在应用的时候更多的应用的是 catch 来解决异样来代替 then 办法的第二个参数,但 catch 也只是一个 then 的语法糖。

从中咱们能够用一些句子来形容 Promise。

  1. promise 是一个类,它的构造函数承受一个函数,函数的两个参数也都是函数
  2. 在传入的函数中执行 resolve 示意胜利,执行 reject 示意失败,传入的值会传给 then 办法的回调函数
  3. promise 有一个叫做 then 的办法,该办法有两个参数,第一个参数是胜利之后执行的回调函数,第二个参数是失败之后执行的回调函数。then 办法在 resolve 或者 reject 执行之后才会执行,并且 then 办法中的值是传给 resolve 或 reject 的参数
  4. promise 反对链式调用

有了相应的形容,接下来就是来一步一步实现了。

简略版 Promise

1. promise 是一个类,它的构造函数承受一个函数,函数的两个参数也都是函数

第一点比较简单

// 这里没有应用 Promise 作为类名是为了不便测试
class WPromise {constructor(executor) {
        // 这里绑定 this 是为了避免执行时 this 的指向扭转,this 的指向问题,这里不过多赘述
        executor(this._resolve.bind(this), this._reject.bind(this));
    }
    
    _resolve() {}
    
    _reject() {}
}

2. 在传入的函数中执行 resolve 示意胜利,执行 reject 示意失败,传入的值会传给 then 办法的回调函数

胜利、失败,这个很容易想到应用一个状态进行标记,实际上 Promise 就是这样做的。在 Promise 中应用了 pending、fulfilled、rejected 来标识以后的状态。

  • pending 初始状态,既不是胜利,也不是失败状态。期待 resolve 或者 reject 调用更新状态。
  • fulfilled 意味着操作胜利实现。
  • rejected 意味着操作失败。

须要留神的一点是,这三个状态之间只存在两个变换关系:

  • pending转换为 fulfilled,只能由resolve 办法实现转换
  • pending转换为 rejected,只能由reject 办法实现转换

传入的值会传给 then 的回调函数,怎么传递呢?显然咱们将对 resolve 和 reject 的值做一个保留。

将下面的状态和值增加到 Promise

class WPromise {
    static pending = 'pending';
    static fulfilled = 'fulfilled';
    static rejected = 'rejected';

    constructor(executor) {executor(this._resolve.bind(this), this._reject.bind(this));
        this.status = WPromise.pending; // 初始化状态为 pending
        this.value = undefined; // 存储 this._resolve 即操作胜利 返回的值
        this.reason = undefined; // 存储 this._reject 即操作失败 返回的值
    }
    
    _resolve(value) {
        this.value = value;
        this.status = WPromise.fulfilled; // 将状态设置为胜利
    }
    
    _reject(reason) {
        this.reason = reason;
        this.status = WPromise.rejected; // 将状态设置为失败
    }
}

3. Promise 有一个叫做 then 的办法,该办法有两个参数,第一个参数是胜利之后执行的回调函数,第二个参数是失败之后执行的回调函数。then 办法在 resolve 或者 reject 执行之后才会执行,并且 then 办法中的值是传给 resolve 或 reject 的参数

这句话有点长,须要留神的是这句 then 办法在 resolve 或者 reject 执行之后才会执行,咱们晓得 Promise 是异步的,也就是说then 传入的函数是不能立马执行,须要存储起来,在 resolve 函数执行之后才拿进去执行。

换句话说,这个过程有点相似于 公布订阅者模式:咱们应用 then 来注册事件,那什么时候来告诉这些事件是否执行呢?答案就是在 resolve 办法执行或者 reject 办法执行时。

ok, 持续欠缺咱们的代码。

class WPromise {
    static pending = "pending";
    static fulfilled = "fulfilled";
    static rejected = "rejected";

    constructor(executor) {executor(this._resolve.bind(this), this._reject.bind(this));
        this.status = WPromise.pending; // 初始化状态为 pending
        this.value = undefined; // 存储 this._resolve 即操作胜利 返回的值
        this.reason = undefined; // 存储 this._reject 即操作失败 返回的值
        // 存储 then 中传入的参数
        // 至于为什么是数组呢?因为同一个 Promise 的 then 办法能够调用屡次
        this.callbacks = [];}

    // onFulfilled 是胜利时执行的函数
    // onRejected 是失败时执行的函数
    then(onFulfilled, onRejected) {
        // 这里能够了解为在注册事件
        // 也就是将须要执行的回调函数存储起来
        this.callbacks.push({
            onFulfilled,
            onRejected,
        });
    }

    _resolve(value) {
        this.value = value;
        this.status = WPromise.fulfilled; // 将状态设置为胜利

        // 告诉事件执行
        this.callbacks.forEach((cb) => this._handler(cb));
    }

    _reject(reason) {
        this.reason = reason;
        this.status = WPromise.rejected; // 将状态设置为失败

        this.callbacks.forEach((cb) => this._handler(cb));
    }

    _handler(callback) {const { onFulfilled, onRejected} = callback;

        if (this.status === WPromise.fulfilled && onFulfilled) {
            // 传入存储的值
            onFulfilled(this.value);
        }

        if (this.status === WPromise.rejected && onRejected) {
            // 传入存储的错误信息
            onRejected(this.reason);
        }
    }
}

这个时候的 Promise 曾经渐具雏形,当初能够来简略测试一下

function fetchData(success) {return new WPromise((resolve, reject) => {setTimeout(() => {if (success) {resolve("willem");
            } else {reject('error');
            }
        }, 1000);
    });
}

fetchData(true).then(data => {console.log(data); // after 1000ms: willem
});

fetchData(false).then(null, (reason) => {console.log(reason); // after 1000ms: error
});

从下面的输入后果来看,临时是没什么问题的。接下来就是须要重点关注的链式调用问题了。

重难点:链式调用

链式调用 不晓得你们看见这个想到了啥,我反正是想到了 jQuery。其实链式调用无非就是再返回一个类的实例,那首先想到的必定就是间接返回this,不过反正本身真的能够吗?

咱们无妨在 then 办法最初增加一行 return this; 来进行一个测试

function fetchData() {return new WPromise((resolve, reject) => {setTimeout(() => {resolve('willem');
        }, 1000);
    });
}

const p1 = fetchData().then(data1 => {return data1 + 'wei'});
const p2 = p1.then((data2) => {console.log(data2);}); // willem 正确输入应该是 'willem wei'
const p3 = p1.then((data3) => {console.log(data3);}); // willem 正确输入应该是 'willem wei'

显然,间接返回 this 是必定不对,必定要对函数的返回值做一个解决。
这时候可能会有同学说了,那我解决不就完事了么,我把 then 回调函数的执行后果赋值给 value 不就完事。答案当然是否定的,这回引发 Promise 外部的 value 和 callbacks 凌乱。

那么,咱们采取的当然是另一个计划,每次 then 办法都将返回一个新的 Promise

这是一个简略的 then 的数据走向。简略说一下,then 函数中返回的 Promise 的 value 值来源于以后 then 函数的 onFulfilled 函数(第一个参数)的执行后果(为不便了解,临时只探讨操作胜利的状况)。

从咱们写的代码来看,value 值只会在 resolve 函数中被赋值,显然咱们也将会把 onFulfilled 执行的后果通过 resolve 的执行来传入到下一个 Promise 中。

退出链式调用的解决:

class WPromise {
    static pending = "pending";
    static fulfilled = "fulfilled";
    static rejected = "rejected";

    constructor(executor) {executor(this._resolve.bind(this), this._reject.bind(this));
        this.status = WPromise.pending; // 初始化状态为 pending
        this.value = undefined; // 存储 this._resolve 即操作胜利 返回的值
        this.reason = undefined; // 存储 this._reject 即操作失败 返回的值
        // 存储 then 中传入的参数
        // 至于为什么是数组呢?因为同一个 Promise 的 then 办法能够调用屡次
        this.callbacks = [];}

    // onFulfilled 是胜利时执行的函数
    // onRejected 是失败时执行的函数
    then(onFulfilled, onRejected) {
        // 返回一个新的 Promise
        return new WPromise((nextResolve, nextReject) => {
            // 这里之所以把下一个 Promsie 的 resolve 函数和 reject 函数也存在 callback 中
            // 是为了将 onFulfilled 的执行后果通过 nextResolve 传入到下一个 Promise 作为它的 value 值
            this.callbacks.push({
                nextResolve,
                nextReject,
                onFulfilled,
                onRejected
            });
        });
    }

    _resolve(value) {
        this.value = value;
        this.status = WPromise.fulfilled; // 将状态设置为胜利

        // 告诉事件执行
        this.callbacks.forEach((cb) => this._handler(cb));
    }

    _reject(reason) {
        this.reason = reason;
        this.status = WPromise.rejected; // 将状态设置为失败

        this.callbacks.forEach((cb) => this._handler(cb));
    }

    _handler(callback) {const { onFulfilled, onRejected, nextResolve, nextReject} = callback;

        if (this.status === WPromise.fulfilled) {
            // 传入存储的值
            // 未传入 onFulfilled 时,将 undefined 传入
            const nextValue = onFulfilled ? onFulfilled(this.value) : undefined;
            nextResolve(nextValue);
            return;
        }

        if (this.status === WPromise.rejected) {
            // 传入存储的错误信息
            // 同样的解决
            const nextReason = onRejected ? onRejected(this.value) : undefined;
            nextReject(nextReason);
        }
    }
}

咱们再把刚开始的例子拿来测试一下

function fetchData() {return new WPromise((resolve, reject) => {setTimeout(() => {resolve('willem');
        }, 1000);
    });
}

fetchData().then((data) => {
    // after 1000ms
    console.log(data); // willem
    return 'wei';
}, (err) => {}).then((data2) => {console.log(data2); // wei
});

哟西,没啥问题。不过下面的版本还有个问题没有解决,当 onFulfilled 执行的后果不是一个简略的值,而就是一个 Promise 时,后续的 then 会期待其执行实现之后才执行。

Promise 根底版的最终版:

class WPromise {
    static pending = "pending";
    static fulfilled = "fulfilled";
    static rejected = "rejected";

    constructor(executor) {executor(this._resolve.bind(this), this._reject.bind(this));
        this.status = WPromise.pending; // 初始化状态为 pending
        this.value = undefined; // 存储 this._resolve 即操作胜利 返回的值
        this.reason = undefined; // 存储 this._reject 即操作失败 返回的值
        // 存储 then 中传入的参数
        // 至于为什么是数组呢?因为同一个 Promise 的 then 办法能够调用屡次
        this.callbacks = [];}

    // onFulfilled 是胜利时执行的函数
    // onRejected 是失败时执行的函数
    then(onFulfilled, onRejected) {
        // 返回一个新的 Promise
        return new WPromise((nextResolve, nextReject) => {
            // 这里之所以把下一个 Promsie 的 resolve 函数和 reject 函数也存在 callback 中
            // 是为了将 onFulfilled 的执行后果通过 nextResolve 传入到下一个 Promise 作为它的 value 值
            this.callbacks.push({
                nextResolve,
                nextReject,
                onFulfilled,
                onRejected
            });
        });
    }

    _resolve(value) {
        // 解决 onFulfilled 执行后果是一个 Promise 时的状况
        // 这里可能了解起来有点艰难
        // 当 value instanof WPromise 时,阐明以后 Promise 必定不会是第一个 Promise
        // 而是后续 then 办法返回的 Promise(第二个 Promise)// 咱们要获取的是 value 中的 value 值(有点绕,value 是个 promise 时,那么外部存有个 value 的变量)// 怎么将 value 的 value 值获取到呢,能够将传递一个函数作为 value.then 的 onFulfilled 参数
        // 那么在 value 的外部则会执行这个函数,咱们只须要将以后 Promise 的 value 值赋值为 value 的 value 即可
        if (value instanceof WPromise) {value.then(this._resolve.bind(this), this._reject.bind(this));
            return;
        }

        this.value = value;
        this.status = WPromise.fulfilled; // 将状态设置为胜利

        // 告诉事件执行
        this.callbacks.forEach((cb) => this._handler(cb));
    }

    _reject(reason) {
        this.reason = reason;
        this.status = WPromise.rejected; // 将状态设置为失败

        this.callbacks.forEach((cb) => this._handler(cb));
    }

    _handler(callback) {const { onFulfilled, onRejected, nextResolve, nextReject} = callback;

        if (this.status === WPromise.fulfilled) {
            // 传入存储的值
            // 未传入 onFulfilled 时,将 undefined 传入
            const nextValue = onFulfilled ? onFulfilled(this.value) : undefined;
            nextResolve(nextValue);
            return;
        }

        if (this.status === WPromise.rejected) {
            // 传入存储的错误信息
            // 同样的解决
            const nextReason = onRejected ? onRejected(this.value) : undefined;
            nextReject(nextReason);
        }
    }
}

ok,测试一下

function fetchData() {return new WPromise((resolve, reject) => {setTimeout(() => {resolve('willem');
        }, 1000);
    });
}

fetchData().then((data) => {
    return new WPromise(resolve => {setTimeout(() => {resolve(data + 'wei');
        }, 1000);
    });
}, (err) => {}).then((data2) => {console.log(data2); // willem wei
});

至此,一个简略的 Promise 就实现了,当然还有很多须要解决,比方异样等等。
下一篇文章咱们一起再来学习一下 finally 和 catch 的实现。

更多:上述模仿 Promise 的残缺代码

退出移动版