前端代码核心实现系列-Promise核心实现

810 阅读6分钟

该系列的主要目的是帮助很多写代码一知半解的同学,认为自己懂了,当真正遇到深入点的问题的时候,还是要靠搜索来解决问题。这个系列的主要目的就是告别搜索,让自己写出的代码能够做到信心满满。不论问题怎么变化我们都能360度无死角解答。

该系列的计划可以在github/core-rewrite上看到。欢迎多多交流。 同时,字节电商杭州团队大量招聘,来加入吧!详细信息可扫最下面公众号

Promise核心实现

这是该代码实现系列的第一期 - Promise核心实现

更多问题欢迎多多在Promise核心实现讨论区进行讨论

前提准备

这一次我们是要实现的是Promise,帮助我们进行更加深入的了解Promise

在这之前需要你理解什么是Promise以及Promise常用的用法,如果你不了解Promise可以点击这里进行了解

写前思考

在实现之前我们要先思考下实现Promise包括哪些点,比如

  • Promise代表异步结果
  • Promise有三种状态,状态只能从pendingfulfilled或者pendingrejected
  • Promise可以链式调用

到这里想到这三个点,我们就可以开始动手实现了,让我们一步步来看

代码实现

构造函数实现

首先,我们来定义一个函数,为了不跟原有的Promise冲突我们命名为CorePromise

function CorePromise(handler) {
    // write code here
 }

首先我们定义Promise的状态,Promsie初始状态是pending

同时我们知道Promise构造函数接受一个参数,该参数是一个函数,它会接受resolvereject两个参数,方便我们进行调用,按这种逻辑我们实现成下面的样子

 function CorePromise(handler) {
    // 三种状态pending, rejected, fulfilled
    this.status = 'pending';

    function resolve() {

    }

    function reject() {

    }

    handler(resolve, reject);
 }

当调用resolvereject的时候会发生什么那?promise的状态会发生变化,我们在之前的基础上实现resolvereject函数,他们只会在pending状态会执行

同时由于是Promise需要异步执行,所以我们这里使用setTimeout进行包裹。

调用后我们记录成功后的结果和失败的原因。

 function CorePromise(handler) {
    // 三种状态pending, rejected, fulfilled
    this.status = 'pending';
    this.value = null;
    this.err = null;

   const resolve = (value) => {
        if (this.status === 'pending') {
            setTimeout(() => {
                this.status = 'fulfilled';
                this.value = value;
            })
        }
    }

    const reject = (err) => {
        if (this.status === 'pending') {
            setTimeout(() => {
                this.status = 'rejected';
                this.err = err;
            })
        }
    }

    handler(resolve, reject);
 }

调用resolvereject除了状态变化,我们还会继续执行调用then里面定义的回调函数,并将结果或失败原因传递给该回调函数。

下面我们定义两个数组来挂载回调函数,并在执行的时候遍历执行它们。

这里另外对 handler(resolve, reject)进行了错误处理,当其直接报错的时候我们将Promise直接reject掉。

function CorePromise(handler) {
    // 三种状态pending, rejected, fulfilled
    this.status = 'pending';
    this.value = null;
    this.err = null;
    this.resolveCbs = [];
    this.rejectCbs = [];

    const resolve = (value) => {
        if (this.status === 'pending') {
            setTimeout(() => {
                this.status = 'fulfilled';
                this.value = value;
                this.resolveCbs.forEach(cb => cb(this.value));
            })
        }
    }

    const reject = (err) => {
        if (this.status === 'pending') {
            setTimeout(() => {
                this.status = 'rejected';
                this.err = err;
                this.rejectCbs.forEach(cb => cb(this.err));
            })
        }
    }

    try {
        handler(resolve, reject);
    } catch(err) {
        reject(err)
    }
 }

到这里我们构造函数实现就差不多了,下面来看then的实现。

then实现

then的主要目标是挂载回调函数,当Promise状态变化的时候,能够执行这些函数。

同时由于then支持链式调用,所以then本身也需要返回一个新的Promise

then接受两个参数,第一个来处理Promise成功后的结果,第二个用来处理Promise失败后的结果,实现上当这两个函数不存在的时候我们赋予其默认值,直接将结果或错误向后传递。

CorePromise.prototype.then = function(resolveCb, rejectCb) {
    let promise = null;
    resolveCb = typeof resolveCb === 'function' ? resolveCb : (value) => value;
    rejectCb = typeof rejectCb === 'function' ? rejectCb : (err) => { throw err };

    promise = new CorePromise((resolve, reject) => {

    });
    
    return promise
}

现在到了最重要的部分,实现then逻辑。这里有两点

  • 如果Promise已经是终态了,则直接进行将then返回的Promse也转变为终态。
  • 如果当前Promise还处在pending状态,那么我们就将处理函数保存在Promise的回调数组中,以供进入终态后调用。

重要:这里涉及到两个Promise实例注意区分,分别是当前Promisethen中返回的新Promise,返回的新PromisePromise状态变化后也进行变化

基本到这里我们核心就实现完毕了,但考虑各种情况我们还需要额外处理下特殊情况

  • then的回调函数结果又是一个Promise实例(这与之前的提到的两种Promise都不同,属于第三个,这里稍微有点绕,需要好好思考清楚)
  • then的回调函数结果的Promise与当前then返回的Promise实例是同一个会造成循环调用,需要抛出错误
CorePromise.prototype.then = function (onFulfilled, onRejected) {CorePromise.prototype.then = function (onFulfilled, onRejected) {
    let promise = null;
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : (value) => value;
    onRejected = typeof onRejected === 'function' ? onRejected : (err) => { throw err };

    const handlePromise = (modifier, resolve, reject) => value => {
        try {
            const x = modifier(value);

            if (x === promise) {
                // 相同对象会循环调用,直接报错
                reject(new TypeError('Chaining cycle detected for promise!'))
                return;
            }

            // x是thenable
            if (x && typeof x.then === 'function') {
                x.then(resolve, reject);
            } else {
                // x不是thenable
                resolve(x);
            }
        } catch(err) {
            reject(err);
        }
    }

    promise = new CorePromise((resolve, reject) => {
        if (this.status === 'fulfilled') {
            handlePromise(onFulfilled, resolve, reject)(this.value);
        } else if (this.status === 'rejected') {
            handlePromise(onRejected, resolve, reject)(this.err);
        } else {
            this.resolveCbs.push(handlePromise(onFulfilled, resolve, reject));
            this.rejectCbs.push(handlePromise(onRejected, resolve, reject));
        }
    });
    
    return promise
}

处理完特殊情况,我们最后再实现先catch函数,它可以获取到Promise调用链中的错误,实现起来也很简单,其实就是then的一个语法糖

CorePromise.prototype.catch = function (onRejected) {
    return this.then(null, onRejected)
}

Promise常用函数实现

我们平时还会常用到的Promise.resolvePromise.rejectPromise.allPromise.race都来实现下。前两者都只不过是一个语法糖

Promise.resolve

Promise.resolve实现,返回一个立即进入resolve状态的Promise

CorePromise.resolve = (value) => {
    return new CorePromise((resolve) => {
        resolve(value)
    })
}

Promise.reject

Promise.reject实现,返回一个立即进入reject状态的Promise

CorePromise.reject = (error) => {
    return new CorePromise((resolve, reject) => {
        reject(error)
    })
}

Promise.all

Promise.all实现起来也不难,有两点需要注意

  1. 当一个Promise进入reject状态,则整个结果Promise进入reject状态
  2. 当所有Promise完成返回结果的时候,注意结果数组的返回顺序,这就是代码里没有直接把结果push进数组而是使用result[i] = res这种方式并配合total计数来实现的原因
CorePromise.all = (promises) => {
    const result = []
    const length = promises.length;
    let total = 0;
    return new CorePromise((resolve, reject) => {
        for (let i = 0; i < length; i++) {
            const promise = promises[i];
            promise.then(res => {
                result[i] = res;
                total++;

                if (total === length) {
                    resolve(result);
                }
            }, (err) => {
                reject(err);
            })
        }
    })
}

Promise.race

Promise.race实现,哪个Promise先执行则直接忽略掉其他Promise

CorePromise.race = (promises) => {
    let isHandled = false
    return new CorePromise((resolve, reject) => {
        for (let i = 0; i < promises.length; i++) {
            promises[i].then((res) => {
                if (!isHandled) {
                    isHandled = true;
                    resolve(res)
                }
            }, reject)
        }
    })
}

好好读书

招聘&前端交流,也可直接加vx: stellarecho