完美经过测试的Promise/A+规范源码分析

Promise/A+ 规范,源码分析

GitHub
promiseA.jpg前端

Promise是前端大厂面试的一道常考题,掌握Promise用法及其相关原理,对你的面试必定有很大帮助。这篇文章主要讲解Promise源码实现,若是你尚未掌握Promise的功能和API,推荐你先去学习一下Promise的概念和使用API,学习知识就要脚踏实地,先把基础搞好才能深入理解源码的实现。
这里推荐阮一峰老师的文章 git

ES6入门-Promise对象 es6

若是你已经掌握了Promise的基本用法,咱们进行下一步github

Promise/A+规范

说到Promise/A+规范,不少同窗可能很不理解这是一个什么东西,下面给出两个地址,不了解的同窗须要先了解一下,对咱们后续理解源码颇有帮助,先看两遍,有些地方看不懂也不要紧,后续咱们能够经过源码来回头再理解,想把一个知识真的学会,就要反复琢磨,从【确定->否认->再确定】不断地深刻理解,直到彻底掌握。 面试

Promise/A+规范英文地址
Promise/A+规范中文翻译 算法

若是你看过了Promise/A+规范,咱们继续,我会带着你们按照规范要求,一步一步的来实现源码npm

Promise/A+ 【2.1】

2.1Promise状态

一个promise必须处于三种状态之一: 请求态(pending), 完成态(fulfilled),拒绝态(rejected)数组

2.1.1 当promise处于请求状态(pending)时
  • 2.1.1.1 promise能够转为fulfilled或rejected状态
2.1.2 当promise处于完成状态(fulfilled)时
  • 2.1.2.1 promise不能转为任何其余状态

2.1.2.2 必须有一个值,且此值不能改变promise

2.1.3 当promise处于拒绝状态(rejected)时
  • 2.1.3.1 promise不能转为任何其余状态
  • 2.1.3.2 必须有一个缘由(reason),且此缘由不能改变

咱们先找需求来完成这一部分代码,一个简单的小架子异步

// 2.1 状态常量
    const PENDING = 'pending';
    const RESOLVED = 'resolved';
    const REJECTED = 'rejected';
    
    // Promise构造函数
    function MyPromise(fn) {
        const that = this;
        this.state = PENDING;
        this.value = null;
        this.resolvedCallbacks = [];
        this.rejectedCallbacks = [];
        function resolve() {
            if (that.state === PENDING) {
    
            }
        }
        function reject() {
            if (that.state === PENDING) {
                
            }
        }
    }

上面这段代码完成了Promise构造函数的初步搭建,包含:

  • 三个状态的常量声明【请求态、完成态、拒绝态】
  • this.state保管状态、this.value保存惟一值
  • resolvedCallbacks 和 rejectedCallbacks 用于保存 then 中的回调,由于当执行完 Promise 时状态可能仍是等待中,这时候应该把 then 中的回调保存起来用于状态改变时使用
  • 给fn的回调函数 reslove、reject
  • resolve、reject确保只有'pedding'状态才能够改变状态

下面咱们来完成resolve和reject

function resolve(value) {
        if (that.state === PENDING) {
            that.state = RESOLVED
            that.value = value
            that.resolvedCallbacks.map(cb => cb(that.value))
        }
    }

    function reject(value) {
        if (that.state === PENDING) {
            that.state = REJECTED
            that.value = value
            that.rejectedCallbacks.map(cb => cb(that.value))
        }
    }
  • 更改this.state的状态
  • 给this.value赋值
  • 遍历回调数组并执行,传入this.value

记下来咱们须要来执行新建Promise传入的函数体

try {
            fn(resolve, reject);
        } catch (e){
            reject(e)
        }

在执行过程当中可能会遇到错误,须要捕获错误传给reject

Promise/A+ 【2.2】

2.2 then方法

promise必须提供then方法来存取它当前或最终的值或者缘由。
promise的then方法接收两个参数:

promise.then(onFulfilled, onRejected)
2.2.1 onFulfilled和onRejected都是可选的参数:
  • 2.2.1.1 若是 onFulfilled不是函数,必须忽略
  • 2.2.1.1 若是 onRejected不是函数,必须忽略
2.2.2 若是onFulfilled是函数:
  • 2.2.2.1 此函数必须在promise 完成(fulfilled)后被调用,并把promise 的值做为它的第一个参数
  • 2.2.2.2 此函数在promise完成(fulfilled)以前绝对不能被调用
  • 2.2.2.2 此函数绝对不能被调用超过一次
2.2.3 若是onRejected是函数:
  • 2.2.3.1 此函数必须在promise rejected后被调用,并把promise 的reason做为它的第一个参数
  • 2.2.3.2 此函数在promise rejected以前绝对不能被调用
  • 2.2.3.2 此函数绝对不能被调用超过一次

现根据这些要求咱们先实现个简单的then函数:

MyPromise.prototype.then = function (onFulfilled, onRejected) {
        const that = this
        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v
        onRejected =
            typeof onRejected === 'function'
                ? onRejected
                : r => {
                    throw r
                }
        if (that.state === PENDING) {
            that.resolvedCallbacks.push(onFulfilled)
            that.rejectedCallbacks.push(onRejected)
        }
        if (that.state === RESOLVED) {
            onFulfilled(that.value)
        }
        if (that.state === REJECTED) {
            onRejected(that.value)
        }
    }
  • 首先判断了传进来的onFulfilled和onRejected是否是一个函数类型,若是不是就建立一个透传数据的函数
  • 判断状态,若是是'pending'就把函数追加到对应的队列中,若是不是'pending',直接执行对应状态的函数【resolves => onFulfilled, rejected => onRejected】

如上咱们就完成了一个简易版的promise,可是还不能彻底知足Promise/A+规范,接下来咱们继续完善

2.2.4 在执行上下文堆栈(execution context)仅包含平台代码以前,不得调用 onFulfilled和onRejected
2.2.5 onFulfilled and onRejected must be called as functions (i.e. with no this value)
2.2.6 then能够在同一个promise里被屡次调用

— 2.2.6.1 若是/当 promise 完成执行(fulfilled),各个相应的onFulfilled回调 必须根据最原始的then 顺序来调用
— 2.2.6.2 若是/当 promise 被拒绝(rejected),各个相应的onRejected回调 必须根据最原始的then 顺序来调用

2.2.7 then必须返回一个promise
promise2 = promise1.then(onFulfilled, onRejected);
  • 2.2.7.1 若是onFulfilled或onRejected返回一个值x, 运行 Promise Resolution Procedure [[Resolve]](promise2, x)
  • 2.2.7.2 若是onFulfilled或onRejected抛出一个异常e,promise2 必须被拒绝(rejected)并把e看成缘由
  • 2.2.7.3 若是onFulfilled不是一个方法,而且promise1已经完成(fulfilled), promise2必须使用与promise1相同的值来完成(fulfiled)
  • 2.2.7.4 若是onRejected不是一个方法,而且promise1已经被拒绝(rejected), promise2必须使用与promise1相同的缘由来拒绝(rejected)

接下来根据规范需求继续完善then函数里的代码:

if (that.status === 'PENDING') {
            promise2 = new Promise(function (resolve, reject) {
                that.resolvedCallbacks.push(function () {
                    setTimeout(function () {
                        try {
                            let x = onFulfilled(that.value);
                            resolutionProcedure(promise2, x, resolve, reject)
                        } catch (e) {
                            reject(e);
                        }
                    })
                });
                that.rejectedCallbacks.push(function () {
                    setTimeout(function () {
                        try {
                            let x = onRejected(that.value);
                            resolutionProcedure(promise2, x, resolve, reject)
                        } catch (e) {
                            reject(e);
                        }
                    })
                });
            })
        }
        if (that.status === 'RESOLVED') {
            promise2 = new Promise(function (resolve, reject) {
                setTimeout(function () {                          //用setTimeOut实现异步
                    try {
                        let x = onFulfilled(that.value);        //x多是普通值 也多是一个promise, 还多是别人的promise                               
                        resolutionProcedure(promise2, x, resolve, reject)  //写一个方法统一处理 
                    } catch (e) {
                        reject(e);
                    }

                })
            })
        }
        if (that.status === 'REJECTED') {
            promise2 = new Promise(function (resolve, reject) {
                setTimeout(function () {
                    try {
                        let x = onRejected(that.value);
                        resolutionProcedure(promise2, x, resolve, reject)
                    } catch (e) {
                        reject(e);
                    }
                })
            })
        }
  • 为了保证函数执行顺序,须要将函数体代码使用 setTimeout 包裹起来
  • 首先咱们返回了一个新的 Promise 对象,并在 Promise 中传入了一个函数
  • 函数的基本逻辑仍是和以前同样,往回调数组中 push 函数
  • 一样,在执行函数的过程当中可能会遇到错误,因此使用了 try...catch 包裹
  • 规范规定,执行 onFulfilled 或者 onRejected 函数时会返回一个 x,而且执行 Promise 解决过程,这是为了避免同的 Promise 均可以兼容使用,好比 JQuery 的 Promise 能兼容 ES6 的 Promise

Promise/A+ 【2.3】

2.3 Promise解决程序

2.3.1 若是promise和x引用同一个对象,则用TypeError做为缘由拒绝(reject)promise。
2.3.2 若是x是一个promise,采用promise的状态
  • 2.3.2.1 若是x是请求状态(pending),promise必须保持pending直到xfulfilled或rejected
  • 2.3.2.2 若是x是完成态(fulfilled),用相同的值完成fulfillpromise
  • 2.3.2.2 若是x是拒绝态(rejected),用相同的缘由rejectpromise
2.3.3另外,若是x是个对象或者方法
  • 2.3.3.1 让x做为x.then
  • 2.3.3.2 若是取回的x.then属性的结果为一个异常e,用e做为缘由reject promise
  • 2.3.3.3 若是then是一个方法,把x看成this来调用它, 第一个参数为 resolvePromise,第二个参数为rejectPromise,其中:

    • 2.3.3.3.1 若是/当 resolvePromise被一个值y调用,运行 [[Resolve]](promise, y)
    • 2.3.3.3.2 若是/当 rejectPromise被一个缘由r调用,用r拒绝(reject)promise
    • 2.3.3.3.3 若是resolvePromise和 rejectPromise都被调用,或者对同一个参数进行屡次调用,第一次调用执行,任何进一步的调用都被忽略
    • 2.3.3.3.4 若是调用then抛出一个异常e,

      • 2.3.3.3.4.1 若是resolvePromise或 rejectPromise已被调用,忽略。
      • 2.3.3.3.4.2 或者, 用e做为reason拒绝(reject)promise
  • 2.3.3.4 若是then不是一个函数,用x完成(fulfill)promise
2.3.4 若是 x既不是对象也不是函数,用x完成(fulfill)promise

若是一个promise被一个thenable resolve,而且这个thenable参与了循环的thenable环,
[[Resolve]](promise, thenable)的递归特性最终会引发[[Resolve]](promise, thenable)再次被调用。
遵循上述算法会致使无限递归,鼓励(但不是必须)实现检测这种递归并用包含信息的TypeError做为reason拒绝(reject)
这部分规范主要描述了resolutionProcedure函数的规范,下面咱们来实现resolutionProcedure这个函数,我先我么你关注2.3.4下面那段话,简单的来讲规定了x不能与promise2相等,这样会发生循环引用的问题,以下栗子:

let p = new MyPromise((resolve, reject) => {
        resolve(1)
    })
    let p1 = p.then(value => {
        return p1
    })

因此咱们须要先进行检测,代码以下:

function resolutionProcedure(promise2, x, resolve, reject) {
        if (promise2 === x) {
            return reject(new TypeError('Error'))
        }
    }

接下来咱们判断x的类型

if (x instanceof MyPromise) {
        x.then(function (value) {
            resolutionProcedure(promise2, value, resolve, reject)
        }, reject)
    }

若是 x 为 Promise 的话,须要判断如下几个状况:

  • 若是 x 处于等待态,Promise 需保持为等待态直至 x 被执行或拒绝
  • 若是 x 处于其余状态,则用相同的值处理 Promise

最后咱们来完成剩余的代码:

let called = false
    if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
        try {
            let then = x.then
            if (typeof then === 'function') {
                then.call(
                    x,
                    y => {
                        if (called) return
                        called = true
                        resolutionProcedure(promise2, y, resolve, reject)
                    },
                    e => {
                        if (called) return
                        called = true
                        reject(e)
                    }
                )
            } else {
                resolve(x)
            }
        } catch (e) {
            if (called) return
            called = true
            reject(e)
        }
    } else {
        resolve(x)
    }
  • 首先建立一个变量 called 用于判断是否已经调用过函数
  • 而后判断 x 是否为对象或者函数,若是都不是的话,将 x 传入 resolve 中
  • 若是 x 是对象或者函数的话,先把 x.then 赋值给 then,而后判断 then 的类型,若是不是函数类型的话,就将 x 传入 resolve 中
  • 若是 then 是函数类型的话,就将 x 做为函数的做用域 this 调用之,而且传递两个回调函数做为参数,第一个参数叫作 resolvePromise ,第二个参数叫作 rejectPromise,两个回调函数都须要判断是否已经执行过函数,而后进行相应的逻辑
  • 以上代码在执行的过程当中若是抛错了,将错误传入 reject 函数中

测试Promise

有专门的测试脚本能够测试所编写的代码是否符合PromiseA+的规范
首先,在promise实现的代码中,增长如下代码:

Promise.defer = Promise.deferred = function () {
        let dfd = {};
        dfd.promise = new Promise((resolve, reject) => {
            dfd.resolve = resolve;
            dfd.reject = reject;
        });
        return dfd;
    }

安装测试脚本:

npm install -g promises-aplus-tests

若是当前的promise源码的文件名为promise.js

那么在对应的目录执行如下命令:

promises-aplus-tests promise.js

共有872条测试用例,能够完美经过
promise_test.jpg

符合Promise/A+规范完整代码

这样咱们就完成了符合Promise/A+规范的源码,下面是整个代码:

const PENDING = 'pending';
    const RESOLVED = 'resolve';
    const REJECTED = 'rejected';
    function Promise(fn) {
        let that = this;
        that.status = 'PENDING';
        that.value = undefined;
        that.resolvedCallbacks = [];
        that.rejectedCallbacks = [];
        function resolve(value) {
            if (value instanceof Promise) {
                return value.then(resolve, reject)
            }
            if (that.status === 'PENDING') {
                that.status = 'RESOLVED';
                that.value = value;
                that.resolvedCallbacks.map(cb => cb(that.value));
            }
        }
        function reject(value) {
            if (that.status === 'PENDING') {
                that.status = 'REJECTED';
                that.value = value;
                that.rejectedCallbacks.map(cb => cb(that.value));
            }
        }
        try {
            fn(resolve, reject);
        } catch (e) {
            reject(e);
        }
    }

    function resolutionProcedure(promise2, x, resolve, reject) {
        //有可能这里返回的x是别人的promise 要尽量容许其余人乱写 
        if (promise2 === x) {//这里应该报一个循环引用的类型错误
            return reject(new TypeError('循环引用'));
        }
        //看x是否是一个promise promise应该是一个对象
        let called;  //表示是否调用过成功或者失败
        if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
            //多是promise 看这个对象中是否有then 若是有 姑且做为promise 用try catch防止报错
            try {
                let then = x.then;
                if (typeof then === 'function') {
                    //成功
                    then.call(x, function (y) {
                        if (called) return        //避免别人写的promise中既走resolve又走reject的状况
                        called = true;
                        resolutionProcedure(promise2, y, resolve, reject)
                    }, function (err) {
                        if (called) return
                        called = true;
                        reject(err);
                    })
                } else {
                    resolve(x)             //若是then不是函数 则把x做为返回值.
                }
            } catch (e) {
                if (called) return
                called = true;
                reject(e)
            }

        } else {  //普通值
            return resolve(x)
        }

    }

    Promise.prototype.then = function (onFulfilled, onRejected) {
        //成功和失败默认不传给一个函数
        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : function (value) {
            return value;
        }
        onRejected = typeof onRejected === 'function' ? onRejected : function (err) {
            throw err;
        }
        let that = this;
        let promise2;  //新增: 返回的promise
        if (that.status === 'PENDING') {
            promise2 = new Promise(function (resolve, reject) {
                that.resolvedCallbacks.push(function () {
                    setTimeout(function () {
                        try {
                            let x = onFulfilled(that.value);
                            resolutionProcedure(promise2, x, resolve, reject)
                        } catch (e) {
                            reject(e);
                        }
                    })
                });
                that.rejectedCallbacks.push(function () {
                    setTimeout(function () {
                        try {
                            let x = onRejected(that.value);
                            resolutionProcedure(promise2, x, resolve, reject)
                        } catch (e) {
                            reject(e);
                        }
                    })
                });
            })
        }
        if (that.status === 'RESOLVED') {
            promise2 = new Promise(function (resolve, reject) {
                setTimeout(function () {                          //用setTimeOut实现异步
                    try {
                        let x = onFulfilled(that.value);        //x多是普通值 也多是一个promise, 还多是别人的promise                               
                        resolutionProcedure(promise2, x, resolve, reject)  //写一个方法统一处理 
                    } catch (e) {
                        reject(e);
                    }

                })
            })
        }
        if (that.status === 'REJECTED') {
            promise2 = new Promise(function (resolve, reject) {
                setTimeout(function () {
                    try {
                        let x = onRejected(that.value);
                        resolutionProcedure(promise2, x, resolve, reject)
                    } catch (e) {
                        reject(e);
                    }
                })
            })
        }

        return promise2;
    }
    Promise.defer = Promise.deferred = function () {
        let dfd = {};
        dfd.promise = new Promise((resolve, reject) => {
            dfd.resolve = resolve;
            dfd.reject = reject;
        });
        return dfd;
    }
    module.exports = Promise;

总结

以上就是符合Promise/A+规范的源码,ES6的Promise其实并非向咱们这样经过js来实现,而是在底层实现,而且还扩展了不少新的方法:

  • Promise.prototype.catch()
  • Promise.prototype.finally()
  • Promise.all()
  • Promise.race()
  • Promise.allSettled()
  • Promise.any()
  • Promise.resolve()
  • Promise.reject()
  • Promise.try()

这里就不一一介绍啦,你们能够参考阮一峰老师的文章 ES6入门-Promise对象
这篇文章给你们讲解的Promise/A+规范的源码,但愿你们能多读多写,深入的体会一下源码的思想,对之后的开发也颇有帮助。
感谢你们的阅读,以为还不错,辛苦点一下关注,谢谢!
pk.jpg

相关文章
相关标签/搜索