阅读 337

手把手教你实现符合PromiseA+规范的promise(测试完美通过)

跑了非常多结果错误的测试,终于让我这份promise通过了测试,感天动地! 天知道我看这个报错看了多少遍了。

image.png

前言

首先是关于PromiseA+规范,可以看我之前的这篇文章,这里就不赘述关于PromsieA+规范了。

关于Promise的总结:使用Promise,告别回调!

然后在手写之前,我们应该清楚的认识到我们要实现什么内容,下面是promise常用的函数,也是我们要一个一个去实现的。

Promise.resolve(value)  --类方法,返回的状态为resolved
Promise.reject(reason)  --类方法,返回的状态为rejected
Promise.prototype.then  --实例方法,注册回调函数
Promise.prototype.catch --实例方法,捕获异常
Promise.race  -- 类方法,返回最先执行的promise任务结果
Promise.all   -- 类方法,返回所有任务成功的结果或者其中一个任务失败的结果
复制代码

Promise构造函数

接着我们开始一步一步来,首先是构造函数。 我们先先定义三个常量表示promise的三个状态。因为后面会经常用到,索性先声明,方便使用。

然后创建我们自定义的函数Promise,传入的参数是执行函数。

const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
function Promise(excutor) {
}
复制代码

先声明我们需要用到的,比如初始化value和reason,以及添加两个数组存储成功和失败的回调。

function Promise(excutor) {
  // 将当前promise对象保存起来,以便resolve和reject里面访问
  const that = this;
  // 给当前promise对象指定初始状态,初始状态为pending
  this.status = PENDING;
  this.value = null; // 初始化value
  this.reason = null; // 初始化reason
  // 构造函数里面添加数组存储成功和失败的回调
  this.onFulfilledCallbacks = [];
  this.onRejectedCallbacks = [];
}
复制代码

接着在在函数里面写我们需要的resolve和reject方法。

function Promise(excutor) {
-------省略上述代码---------
    // resolve方法参数是value
    function resolve(value) {
        // 如果当前状态不是pending,直接结束
        if (that.status !== PENDING) {
            return
        }
        // 如果当前状态是pending,进行下一步
        // 将状态改成fulfilled
        that.status = FULFILLED;
        // 保存value数据
        that.value = value;

        // 如果有待执行callback函数,立即执行回调函数
        if (that.onFulfilledCallbacks.length > 0) {
            // 遍历数组将所有成功的回调拿出来执行
            that.onFulfilledCallbacks.forEach(callback => {
                callback(that.value);
            });
        }

    }
     // reject方法参数是reason
    function reject(reason) {
        // 如果当前状态不是pending,直接结束
        if (that.status !== PENDING) {
            return
        }
        // 将状态改成rejected
        that.status = REJECTED;
        // 保存reason数据
        that.reason = reason;
        // 判断是否有待执行函数
        if (that.onRejectedCallbacks.length > 0) {
            // resolve里面将所有失败的回调拿出来执行
            that.onRejectedCallbacks.forEach(callback => {
                callback(that.reason);
            });
        }

    }
}
复制代码

实现了resolve和reject之后执行传入的函数

    //立即同步执行excutor
    try {
        excutor(resolve, reject);
    } catch (error) { // 如果执行器抛出异常,那promise对象变为rejected状态
        reject(error);
    }
复制代码

到这里基本上就实现了遵循PromiseA+规范的promise构造函数。完整的代码会在文末给出。

Promise解决过程

根据PromiseA+规范来看,我们还需要写一个Promise解决过程。

Promise 解决过程 是一个抽象的操作,其需输入一个 promise 和一个值,我们表示为 [[Resolve]](promise, x),如果 x 有 then 方法且看上去像一个 Promise ,解决程序即尝试使 promise 接受 x 的状态;否则其用 x 的值来执行 promise 。

function resolvePromise(promise, x, resolve, reject) {
    // 如果 promise 和 x 指向同一对象,以 TypeError 为据因拒绝执行 promise
    // 这是为了防止死循环
    if (promise === x) {
        return reject(new TypeError('The promise and the return value are the same'));
    }

    if (x instanceof Promise) {
        // 如果 x 为 Promise ,则使 promise 接受 x 的状态
        // 也就是继续执行x,如果执行的时候拿到一个y,还要继续解析y
        // 这个if跟下面判断then然后拿到执行其实重复了,可有可无
        x.then(function(y) {
            resolvePromise(promise, y, resolve, reject);
        }, reject);
    }
    // 如果 x 为对象或者函数
    else if (typeof x === 'object' || typeof x === 'function') {
        if (x === null) {
            return resolve(x);
        }

        try {
            // 把 x.then 赋值给 then 
            var then = x.then;
        } catch (error) {
            // 如果取 x.then 的值时抛出错误 e ,则以 e 为据因拒绝 promise
            return reject(error);
        }

        // 如果 then 是函数
        if (typeof then === 'function') {
            var called = false;
            try {
                then.call(
                    x,
                    function(y) {
                        // 如果 resolvePromise 和 rejectPromise 均被调用,
                        // 或者被同一参数调用了多次,则优先采用首次调用并忽略剩下的调用
                        // 实现这条需要前面加一个变量called
                        if (called) return;
                        called = true;
                        resolvePromise(promise, y, resolve, reject);
                    },
                    // 如果 rejectPromise 以据因 r 为参数被调用,则以据因 r 拒绝 promise
                    function(r) {
                        if (called) return;
                        called = true;
                        reject(r);
                    });
            } catch (error) {
                if (called) return;
                reject(error);
            }
        } else {
            // 如果 then 不是函数,以 x 为参数执行 promise
            resolve(x);
        }
    } else {
        // 如果 x 不为对象或者函数,以 x 为参数执行 promise
        resolve(x);
    }
}
复制代码

这里只需要根据规范一步一步实现即可。此处参考了这篇文章。

Promise原型对象的then()

Promise的then方法必须返回一个promise对象

promise2 = promise1.then(onFulfilled, onRejected);
复制代码

1、如果 onFulfilled 或者 onRejected 返回一个值 x ,则运行下面的 Promise 解决过程:[[Resolve]](promise2, x)

2、如果 onFulfilled 或者 onRejected 抛出一个异常 e ,则 promise2 必须拒绝执行,并返回拒因 e

3、如果 onFulfilled 不是函数且 promise1 成功执行, promise2 必须成功执行并返回相同的值

4、如果 onRejected 不是函数且 promise1 拒绝执行, promise2 必须拒绝执行并返回相同的据因

首先我们创建一个then方法,挂载在原型链上,因为then方法是Promise原型对象的方法。

另外,Promise的状态有三种:等待态(Pending)、执行态(Fulfilled)和拒绝态(Rejected), 所以我们分情况来处理。

Promise.prototype.then = function(onFulfilled, onRejected) {
    const that = this; // 保存一下this
    if (this.status === FULFILLED) {
      
    }
    if (this.status === REJECTED) {
      
    }
    if (this.status === PENDING) {
      
    }
}
复制代码

如果当前是FULFILLED状态,异步执行onResovle并改变return的promise状态;

如果当前是REJECTED状态,异步执行onReject并改变return的promise状态;

如果还是PENDING状态,我们就将回调保存下来。

接下来我就写一个fulfilled状态来作为例子,注意最终返回一个新的Promise对象

    if (this.status === FULFILLED) {
        var promise2 = new Promise((resolve, reject) => {
            setTimeout(function() {
                try {
                    // 如果抛出异常,return的promise就会失败,reason就是error
                    // 如果回调函数执行返回非promise,return的promise就会成功,value就是返回的值
                    // 如果回调函数执行返回是promise,return的promise的结果就是这个promise的结果
                    if (typeof onFulfilled !== 'function') {
                        resolve(that.value);
                    } else {
                        var x = realOnFulfilled(that.value);
                        resolvePromise(promise2, x, resolve, reject);
                    }
                } catch (error) {
                    reject(error);
                }
            }, 0);
        })
        return promise2; //返回一个新的promise对象
    }
复制代码

而状态是rejected的时候也是同理,当状态是pending的时候其实就是合并一下前面两个状态。做到这里的时候then方法基本上也完成了。

但是要注意,我们缺少了判断传入的参数是不是函数。因为有可能传的是数字或者其他类型的值。所以我们需要在前面加一个判断。

    var realOnFulfilled = onFulfilled;
    if (typeof realOnFulfilled !== 'function') {
        realOnFulfilled = function(value) {
            return value;
        }
    }
    
    var realOnRejected = onRejected;
    if (typeof realOnRejected !== 'function') {
        realOnRejected = function(reason) {
            throw reason;
        }
    }
复制代码

Promise原型对象的catch()

catch()就是指定失败的回调函数,然后返回一个新的promise对象。 同样的,也是挂载在原型链上,然后直接通过我们刚刚写的then方法,返回新的promise对象。

Promise.prototype.catch = function(onRejected) {
    return this.then(undefined, onRejected)
}
复制代码

Promise函数对象的resolve方法

resolve方法就是返回一个指定结果的成功的promise。并且,当传入的是promise对象时,这个对象就是promise的结果。反之,如果是传数字之类的,直接把promsie状态变成成功处理的状态,数据就是传入的值。

Promise.resolve = function(value) {
    // 返回一个成功/失败的promise
    return new Promise((resolve, reject) => {
        // 判断value是否为promise
        if (value instanceof Promise) { // value是promise => 使用value的结果作为promise的结果
            value.then(resolve, reject)
        } else { // value不是promise => promise变为成功,数据是value
            resolve(value)
        }
    })
}
复制代码

Promise函数对象的reject方法

reject方法返回一个指定reason的失败的promise。

写过了上面的resolve方法,我们就可以很容易的写出reject方法了。

Promise.reject = function(reason) {
    // 返回一个失败的promise
    return new Promise((resolve, reject) => {
        reject(reason)
    })
}
复制代码

Promise函数对象的all方法

all方法就是返回一个promise,并且,只有当所有promise都成功时才成功,否则失败。

Promise.all = function(promises) {
    // 用来保存所有成功value的数组
    const values = new Array(promises.length)

    // 用来保存成功promise的数量
    let resolvedCount = 0 // 计数器
    return new Promise((resolve, reject) => {
        if (promises.length === 0) {
            return resolve(values)
        }
        // 遍历promises获取每个promise的结果
        promises.forEach((p, index) => {
            p.then(
                value => {
                    resolvedCount++ //成功的数量+1
                    // p成功,将成功的value保存到values
                    // values.push(value)
                    values[index] = value

                    // 如果全部成功了,将return的promise改变成功
                    if (resolvedCount === promises.length) {
                        resolve(values)
                    }
                },
                reason => { // 只要有一个失败了,return的promise就失败
                    reject(reason)

                }
            )

        })
    })
}
复制代码

这里的难点就是,需要用到一个计数器,也就是一个变量来保存所有成功的promsie的数量,每次promise成功时计数器加一,当成功的promise的数量跟传入的promise数组的数量相同,也就是代表所有的promise都成功了

Promise函数对象的race方法

race方法返回一个promise,其结果由第一个完成的promise结果决定。 由第一个完成的promise结果决定就比较简单了,我们只需要遍历传进来的数组,一旦有成功的,promise的状态就可以修改成成功,一旦有失败的,promise的状态就可以修改为失败。

Promise.race = function(promises) {
    // 返回一个promise
    return new Promise((resolve, reject) => {
        if (promises.length === 0) {
            return resolve()
        }
        // 遍历promises获取每个promise的结果
        promises.forEach((p, index) => {
            Promise.resolve(p).then(
                value => { // 一旦有成功了,将return的promise状态变为成功
                    resolve(value)
                },
                reason => { // 只要有一个失败了,return的promise就失败
                    reject(reason)
                }
            )
        })
    })
}
复制代码

promise测试方法以及过程

做到这里其实我们已经基本上是完成了常用的方法。可以先来测试一波。 这里我用的是promiseA+规范官方测试工具promises-aplus-tests。 根据里面的文档,我们需要先定义一个静态方法deferred,

Promise.deferred = function() {
    var result = {};
    result.promise = new Promise(function(resolve, reject) {
        result.resolve = resolve;
        result.reject = reject;
    });

    return result;
}
复制代码

然后我们把这个包下载下来,直接npm下载即可。

npm install promises-aplus-tests -g
复制代码

下载完之后可以看到有个文件夹,

image.png

然后再配置一下里面的package.json

{
  "devDependencies": {
    "promises-aplus-tests": "^2.1.2"
  },
  "scripts": {
    "test": "promises-aplus-tests Promise"
  }
}
复制代码

这样我们就可以进行测试了,在终端上运行以下命令:

promises-aplus-tests Promise.js
复制代码

promise测试结果

如果写的promise没有问题的话,可以看到下面的结果,通过了官方的872个测试用例。

image.png

结语

本文的完整代码附上:github.com/nankou/stud…

当然,我这份手写的promise绝对不是最好最便捷最准确的,如果大家有发现什么错误,欢迎大家批评指正。希望大家一起进步!

文章分类
前端
文章标签