Promise——从浏览文档到简朴完成(二)

媒介

根据文档申明简朴地完成 ES6 Promise的各个要领并不难,然则Promise的一些特别需求完成起来并不简朴,我起首提出一些不好完成或许轻易疏忽的需求:

  • 数据通报
  • 回调绑定
  • 将回调变成 microtask
  • 完成 then/finally 返回的pending promise “追随”它们的回调返回的pending promise
  • 完成 resolve 返回的 promise “追随”它的thenable对象参数

完成框架

在处置惩罚上述题现在,我们先完成一个框架。
起首,我的目标是完成一个Promise插件,它包含:

  • 组织函数:Promise
  • 静态要领:resolve、reject、all 和 race
  • 实例要领:then、catch 和 finally
  • 私有函数:identity、thrower 和 isSettled 等

以下:

;(function() {
    function Promise(executor) {
    }
    
    Object.defineProperties(Promise, {
        resolve: {
            value: resolve,
            configurable: true,
            writable: true
        },
        reject: {
            value: reject,
            configurable: true,
            writable: true
        },
        race: {
            value: race,
            configurable: true,
            writable: true
        },
        all: {
            value: all,
            configurable: true,
            writable: true
        }
    });
    
    Promise.prototype = {
        constructor: Promise,
        
        then: function(onFulfilled, onRejected) {
        },
        
        catch: function(onRejected) {
        },
        
        finally: function(onFinally) {
        },
    }
    
    function identity(value) {
        return value;
    }
    
    function thrower(reason) {
        throw reason;
    }
    
    function isSettled(pro) {
        return pro instanceof Promise ? pro.status === 'fulfilled' || pro.status === 'rejected' : false;
    }
    
    window.Promise = Promise;
})();

处置惩罚题目

接下来,我们处置惩罚各个题目。

数据通报

为了通报数据——回调函数须要用到的参数以及 promise 的状况,我们起首在组织函数Promise中给新天生的对象增加statusvaluereason属性,并且在组织函数中实行 executor 函数:

function Promise(executor) {
    var self = this;

    this.status = 'pending';
    this.value = undefined;
    this.reason = undefined;

    typeof executor === 'function' ? executor.call(null,
    function(value) {
        self.value = value;
        self.status = 'fulfilled';
    },
    function(reason) {
        self.reason = reason;
        self.status = 'rejected';
    }) : false;
}

我们将 value、reason 和 status 保存在 Promise 对象中,如许,我们就能够在 Promise 对象的要领中经由历程this(即 Promise 对象的援用)来访问这些数据,并将其用作回调函数的参数。

根据文档申明,为了完成链式挪用,Promise的一切要领都邑返回一个 Promise 对象,而且除了Promise.resolve(peomiseObj) 这类状况外都是新天生的 Promise 对象。所以接下来我的大部分要领都邑返回一个新的 promise 对象。不天生新对象的惯例:

var a = Promise.resolve('a'),
    b = Promise.resolve(a);
console.log(a === b)    //true

回调绑定

接下来,我们要将thencatchfinally中的回调要领绑定到Promise对象的状况转变这个事宜上。
我想到的第一个事宜就是onchange事宜,然则 promiseObj.status 属性上并没有change事宜。然则,我立时想到每次设置accessor属性的值时,就会挪用 accessor 属性的setter要领。那末,我只要把status属性设置为存取属性,然后在它的 setter 要领里触发绑定的回调函数就行啦!以下:

function Promise(executor) {
    var self = this;

    //存储状况的私有属性
    this._status = 'pending';

    this.value = undefined;
    this.reason = undefined;
    //this.events = new Events();

    //存储状况的公然属性
    Object.defineProperty(this, 'status', {
        get: function() {
            return self._status;
        },
        set: function(newValue) {
            self._status = newValue;
            //self.events.fireEvent('change');
        },
        configurable: true
    });

    typeof executor === 'function' ? executor.call(null,
    function(value) {
        self.value = value;
        self.status = 'fulfilled';
    },
    function(reason) {
        self.reason = reason;
        self.status = 'rejected';
    }) : false;
}

为了绑定回调函数,我运用了宣布定阅形式。在thencatchfinally要领实行的时刻定阅事宜change,将本身的回调函数绑定到change事宜上,promiseObj.status 属性是宣布者,一旦它的值发作转变就宣布change事宜,实行回调函数。
为了节约篇幅,不那末主要的宣布者Events() 组织函数及其原型我就不贴代码了,文章末端我会给出源代码。

完成 microtask

thencatchfinally要领的回调函数都是microtask,当满足前提(promise 对象状况转变)时,这些回调会被放入microtask行列。每当挪用栈中的macrotask实行终了时,马上实行microtask行列中一切的microtask,如许一次事宜轮回就完毕了,js引擎守候下一次轮回。
要我完成microtask我是做不到的,我就只能用macrotask模仿一下microtask了。
我用 setTimeout 宣布的macrotask举行模仿:

Object.defineProperty(this, 'status', {
    get: function() {
        return self._status;
    },
    set: function(newValue) {
        self._status = newValue;
        setTimeout(() => {
            self.events.fireEvent('change');
        },
        0);
    },
    configurable: true
});

完成函数

接下来,我们完成各个函数和要领。在晓得要领的参数和返回值后再完成要领若有神助,而完成历程中最难处置惩罚的就是 pending 状况的 promise 对象,因为我们要等它变成别的状况时,再做真正的处置惩罚。下面我拿出两个最具代表性的要领来剖析。

静态要领all

假如忘记了 Promise.all(iterable) 的参数和返回值,能够返回我上一篇文章检察。

function all(iterable) {
    //假如 iterable 不是一个可迭代对象
    if (iterable[Symbol.iterator] == undefined) {
        let err = new TypeError(typeof iterable + iterable + ' is not iterable (cannot read property Symbol(Symbol.iterator))');
        return Promise.reject(err);
    }

    //假如 iterable 对象为空
    if (iterable.length === 0) {
        return Promise.resolve([]);
    }

    //别的状况用异步处置惩罚
    var pro = new Promise(),    //all 返回的 promise 对象
        valueArr = [];         //all 返回的 promise 对象的 value 属性
    setTimeout(function() {
        var index = 0,     //纪录当前索引
        count = 0,
        len = iterable.length;

        for (let val of iterable) { -
            function(i) {
                if (val instanceof Promise) { //当前值为 Promise 对象时
                    if (val.status === 'pending') {
                        val.then(function(value) {
                            valueArr[i] = value;
                            count++;
                            //Promise.all([new Promise(function(resolve){setTimeout(resolve, 100, 1)}), 2, 3, 4])
                            if (count === len) {
                                pro.value = valueArr;
                                pro.status = 'fulfilled';
                            }
                        },
                        function(reason) {
                            pro.reason = reason;
                            pro.status = 'rejected';
                            //当一个pending Promise起首完成时,消除别的 pending Promise的事宜,防备以后别的 Promise 转变 pro 的状况
                            for (let uselessPromise of iterable) {
                                if (uselessPromise instanceof Promise && uselessPromise.status === 'pending') {
                                    uselessPromise.events.removeEvent('change');
                                }
                            }
                        });
                    } else if (val.status === 'rejected') {
                        pro.reason = val.reason;
                        pro.status = 'rejected';
                        return;
                    } else {
                        //val.status === 'fulfilled'
                        valueArr[i] = val.value;
                        count++;
                    }
                } else {
                    valueArr[i] = val;
                    count++;
                }
                index++;
            } (index);
        }

        //假如 iterable 对象中的 promise 对象都变成 fulfilled 状况,或许 iterable 对象内没有 promise 对象,
        //因为我们能够须要守候 pending promise 的效果,所以要分外消费一个变量计数,而不能用valueArr的长度推断。
        if (count === len) {
            pro.value = valueArr;
            pro.status = 'fulfilled';
        }
    },
    0);

    return pro;
}

这里诠释两点:

1、怎样保证 value 数组中值的递次
假如iterable对象中的 promise 对象都变成 fulfilled 状况,或许 iterable 对象内没有 promise 对象,all 返回一个 fulfilled promise 对象,且其 value 值为 iterable 中各项值构成的数组,数组中的值将会根据 iterable 内的递次排列,而不是由 pending promise 的完成递次决议。
为了保证 value 数组中值的递次,最简朴的要领是

valueArr[iterable.indexOf(val)] = val.value;

然则像除 Array、TypedArray 和 String 外的 Map 和 Set 原生 iterabe 对象,以及别的经由历程myIterable[Symbol.iterator] 建立的自定义的 iterable 对象都没有 indexOf 要领,所以我挑选用闭包来保证 value 数组值的递次。

2、处置惩罚 pending promise 对象。
pending promise 是致使这个函数要分外增加许多变量存储状况,分外做许多推断和处置惩罚的罪魁祸首。
假如 iterabe 对象中有一个pending状况的 promise(一般为一个异步的 promise),我们就运用then要领来延续关注它的动态。

  • 一旦它变成fulfilledpromise,就将它的 value 到场 valueArr 数组。我们增加一个 count 变量纪录现在 valueArr 猎取到了多少个值,当悉数猎取到值后,就能够给 pro.value 和pro.status 赋值了。之所以用 count 而不是 valueArr.length 推断,是因为 valueArr = [undefined,undefined,undefined,1] 的长度也为4,如许能够致使还没猎取到 pending promise 的值就转变 pro.status 了。
  • 而当它变成rejectedpromise 时,我们就更新 all 要领返回的对象的 reason 值,同时转变状况 status 为 rejected,触发绑定的onrejected函数。别的,为了与原生 Promise 表现雷同:假如 iterable 对象中恣意一个 pending promise 对象状况变成 rejected,将不再延续关注别的 pending promise 的动态。而我早就在一切的 pending promise 上都绑定了 onfulfilled 和 onrejected 函数,用来跟踪它们。所以我须要在某个 pending promise 变成 rejected promise 时,删除它们绑定的回调函数。

实例要领then

Promise.prototype.then(onFulfilled, onRejected):

Promise.prototype.then = function(onFulfilled, onRejected) {
    var pro = new Promise();

    //绑定回调函数,onFulfilled 和 onRejected 用一个回调函数处置惩罚
    this.events.addEvent('change', hander.bind(null, this));

    function hander(that) {
        var res; //onFulfilled 或 onRejected 回调函数实行后获得的效果
        try {
            if (that.status === 'fulfilled') {
                //假如onFulfilled不是函数,它会在then要领内部被替换成一个 Identity 函数
                typeof onFulfilled !== 'function' ? onFulfilled = identity: false;
                //将参数 this.value 传入 onFulfilled 并实行,将效果赋给 res
                res = onFulfilled.call(null, that.value);
            } else if (that.status === 'rejected') {
                //假如onRejected不是函数,它会在then要领内部被替换成一个 Thrower 函数
                typeof onRejected !== 'function' ? onRejected = thrower: false;

                res = onRejected.call(null, that.reason);
            }
        } catch(err) {
            //抛出一个毛病,状况3
            pro.reason = err;
            pro.status = 'rejected';

            return;
        }

        if (res instanceof Promise) {
            if (res.status === 'fulfilled') {            //状况4
                pro.value = res.value;
                pro.status = 'fulfilled';
            } else if (res.status === 'rejected') {      //状况5
                pro.reason = res.reason;
                pro.status = 'rejected';
            } else {                                     //状况6
                //res.status === 'pending'时,pro 追随 res
                pro.status = 'pending';
                res.then(function(value) {
                    pro.value = value;
                    pro.status = 'fulfilled';
                },
                function(reason) {
                    pro.reason = reason;
                    pro.status = 'rejected';
                });
            }
        } else {
            //回调函数返回一个值或不返回任何内容,状况1、2
            pro.value = res;
            pro.status = 'fulfilled';
        }
    }

    return pro;
};

我想我已解释得很清晰了,能够对比我上一篇文章举行浏览。
我再申明一下pending promise 的“追随”状况,和 all 要领的完成体式格局差不多,这里也是用 res.then来“追随”的。我置信人人都看得懂代码,下面我举个例子来实践一下:

var fromCallback;

var fromThen = Promise.resolve('done')
.then(function onFulfilled(value) {
    fromCallback = new Promise(function(resolve){
        setTimeout(() => resolve(value), 0);    //未实行 setTimeout 的回调要领之前 fromCallback 为'pending'状况
    });
    return fromCallback;    //then 要领返回的 fromThen 将追随 onFulfilled 要领返回的 fromCallback
});

setTimeout(function() {
    //现在已实行完 onFulfilled 回调函数,fromCallback 为'pending'状况,fromThen ‘追随’ fromCallback
    console.log(fromCallback.status);    //fromCallback.status === 'pending'
    console.log(fromThen.status);        //fromThen.status === 'pending'
    setTimeout(function() {
        //现在已实行完 setTimeout 中的回调函数,fromCallback 为'fulfilled'状况,fromThen 也随着变成'fulfilled'状况
        console.log(fromCallback.status + ' ' + fromCallback.value);    //fromCallback.status === 'fulfilled'
        console.log(fromThen.status + ' ' + fromThen.value);        //fromThen.status === 'fulfilled'
        console.log(fromCallback === fromThen);        //false
    }, 10);    //将这个 delay 参数改成 0 尝尝
}, 0);

看完这个例子,我置信人人都搞懂了then的回调函数返回 pending promise 时它会怎样处置惩罚了。
别的,这个例子也体现出我用 setTimeout 分发的macrotask模仿microtask的不足之处了,假如将倒数第二行的的 delay 参数改成 0,那末 fromThen.status === ‘pending’,这申明修正它状况的代码在 log 它状况的代码以后实行,至于缘由人人本身想一下,这涉及到 event loop。

测试

列位大侠请点下面的链接举行测试:
https://codepen.io/lyl123321/…

或许直接点这里检察源代码:
https://github.com/lyl123321/…
新增 Promise.try:
https://github.com/lyl123321/…

    原文作者:宗介
    原文地址: https://segmentfault.com/a/1190000018530433
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞