美文网首页RN iOS开发相关程序员开源工具技巧
【转向JavaScript系列】深入理解Promise

【转向JavaScript系列】深入理解Promise

作者: ronniegong | 来源:发表于2017-01-22 12:55 被阅读1038次

本文缘起前段时间一朋友换工作时,笔试题中要求手写一个Promise。在工作中虽已大量使用Promise,其原理却没有深入探究过,换做自己,当场也很难手写一个完善的Promise实现。近期补了一些课,以本文来彻底的理解Promise实现原理。

1.Promise是什么

Promise是抽象异步处理对象以及对其进行各种操作的组件,可以将复杂的异步处理轻松的进行模式化。
使用Promise进行异步处理的一个例子

function getUserId() {
    return new Promise(function (resolve, reject) {
        // 异步请求
        Y.io('/userid/1', {
            on: {
                success: function (id, res) {
                    var o = JSON.parse(res);
                    if (o.status === 1) {
                        resolve(o.id);
                    } else {
                        // 请求失败,返回错误信息
                        reject(o.errorMsg);
                    }
                }
            }
        });
    });
}

getUserId().then(function (id) {
    // do sth with id
}, function (error) {
    console.log(error);
});

如对Promise的使用尚不了解,推荐阅读JavaScript Promise Cookbook中文版

在掌握了Promise的使用后,推荐继续阅读Promise规范Promise A+ 规范

2.Promise实现原理

理解实现原理过程中,阅读了不少相关文章,其中剖析 Promise 之基础篇JS Promise的实现原理这两篇文章,个人认为质量较高。本文对原理的理解,也是基于这两篇文章。

借用MDN的图,看看Promise的状态迁移,每个 Promise 存在三个互斥状态:pending、fulfilled、rejected,它们之间的关系是:


2.1 Promise简易实现

最初我是阅读剖析 Promise 之基础篇来学习的,文中初始实现了一个简易版的Promise

function Promise(fn) {
    var state = 'pending',
        value = null,
        deferreds = [];

    this.then = function (onFulfilled) {
        if (state === 'pending') {
            deferreds.push(onFulfilled);
            return this;
        }
        onFulfilled(value);
        return this;
    };

    function resolve(newValue) {
        value = newValue;
        state = 'fulfilled';
        setTimeout(function () {
            deferreds.forEach(function (deferred) {
                deferred(value);
            });
        }, 0);
    }

    fn(resolve);
}

function getUserId() {
    return new Promise(function (resolve) {
        resolve(123);
    });
}

getUserId().then(function (id) {
        console.log('do sth with', id);
    });

这一版本的实现,还是很好理解的。

  • Promise初始状态为pending、value为null、延迟队列为空。并且作为函数作用域的变量,不向外暴露
  • 传入Promise的函数fn立即执行,将resolve传入fn,fn执行完成调用resolve
  • resolve被定义为一个内部函数,使用闭包方式来访问value、state、deferreds。遵循Promise规范,resolve方法中,采用异步方式执行延迟队列的方法
  • promise对象上添加then方法,当前promise对象状态为pending时,将通过then方法注册的新方法,添加到延迟队列;当前promise对象状态为完成时,执行注册的方法

2.2 串行Promise

上述简易版本的实现,相信理解起来无压力。但是在理解剖析 Promise 之基础篇文中串行Promise时,着实费了一番脑筋。

function getUserId() {
    return new Promise(function (resolve) {
        window.setTimeout(function () {
            resolve(9876);
        });
    });
}

function getUserMobileById(id) {
    return new Promise(function (resolve) {
      console.log('start to get user mobile by id:', id);
        window.setTimeout(function () {
            resolve(13810001000);
        });
    });
}

getUserId()
    .then(getUserMobileById)
    .then(function (mobile) {
        console.log('do sth with', mobile);
    });


function Promise(fn) {
    var state = 'pending',
        value = null,
        deferreds = [];
        
    this.then = function (onFulfilled) {
        return new Promise(function (resolve) {
            handle({
                onFulfilled: onFulfilled || null,
                resolve: resolve
            });
        });
    };

    function handle(deferred) {
        if (state === 'pending') {
            deferreds.push(deferred);
            return;
        }
    
        var ret = deferred.onFulfilled(value);
        deferred.resolve(ret);
    }
    
    function resolve(newValue) {
        if (newValue && (typeof newValue === 'object' || typeof newValue === 'function')) {
            var then = newValue.then;
            if (typeof then === 'function') {
                then.call(newValue, resolve);
                return;
            }
        }
        state = 'fulfilled';
        value = newValue;
        setTimeout(function () {
            deferreds.forEach(function (deferred) {
                handle(deferred);
            });
        }, 0);
    }

    fn(resolve);
}

初看上述代码时,跟着原文叙述,没能彻底理解其执行流程。通过不断的断点调试,才最终理解。在这一过程中,明白了下面几点

  • Promise的执行过程可以分为两个阶段,即初始时注册阶段和完成时的resolve阶段
  • 初始时,通过promise.then注册的方法,保存在promise对象的延迟队列。每次调用then方法,返回一个新promis实例,作为链式调用的桥接,这类promise可以乘坐bridge promise
  • 注册的方法执行完,执行resolve时。从当前promise对象延迟队列取出注册的方法继续执行。当注册的方法生成一个新promise实例时,调用then方法注册到对应的延迟队列中;否则依次resolve链式调用中相应的promise实例

将上述过程表达如下

理解上述执行流程,再为上述实现加上错误执行过程和异常处理

function Promise(fn) {
    var state = 'pending',
        value = null,
        deferreds = [];

    this.then = function (onFulfilled, onRejected) {
        return new Promise(function (resolve, reject) {
            handle({
                onFulfilled: onFulfilled || null,
                onRejected: onRejected || null,
                resolve: resolve,
                reject: reject
            });
        });
    };

    function handle(deferred) {
        if (state === 'pending') {
            deferreds.push(deferred);
            return;
        }

        var cb = state === 'fulfilled' ? deferred.onFulfilled : deferred.onRejected,
                ret;
        if (cb === null) {
            cb = state === 'fulfilled' ? deferred.resolve : deferred.reject;
            cb(value);
            return;
        }
        try {
            ret = cb(value);
            deferred.resolve(ret);
        } catch (e) {
            deferred.reject(e);
        }
    }

    function resolve(newValue) {
        if (newValue && (typeof newValue === 'object' || typeof newValue === 'function')) {
            var then = newValue.then;
            if (typeof then === 'function') {
                then.call(newValue, resolve, reject);
                return;
            }
        }
        state = 'fulfilled';
        value = newValue;
        finale();
    }

    function reject(reason) {
        state = 'rejected';
        value = reason;
        finale();
    }

    function finale() {
        setTimeout(function () {
            deferreds.forEach(function (deferred) {
                handle(deferred);
            });
        }, 0);
    }

    fn(resolve, reject);
}

3.更好的实现

通过阅读剖析 Promise 之基础篇,跟进上述代码执行过程,相信已经可以理解Promise实现原理。

在理解过程中,让我思考,上述执行流程到底是哪里不易理解,有没有更好的实现呢?

在上述的实现中,关键方法resolve被定义在Promise中作为内部函数,通过闭包获取promise对象的变量的引用。再将回调函数和resolve方法注册到延迟队列,通过resolve完成了链式的回调。这一过程隐式调用太多,不好理解。

继续阅读其他文章,认为这篇JS Promise的实现原理中有更好的实现。其完整实现可以查看这里

3.1 代码结构

构造函数定义如下

function Promise(resolver) {
    this._status = 'pending';

    this._doneCallbacks = [];
    this._failCallbacks = [];

    resolver(resolve, reject);
    ...
}

在 promise 对象中定义了成功回调和失败回调的两个数组,数组中的每一项是通过内部封装的闭包函数调用的结果,也是一个函数。这个函数可以访问到内部调用闭包时传递的 promise 对象,因此通过这种方式也可以访问到我们需要的下一个 promise 以及其关联的成功、失败回调的引用。在then方法中增加闭包调用以及为前一个 promise 对象保存引用。

Promise.prototype.then = function(onResolve, onReject) {
    var promise = new Promise(function() {});

    this._doneCallbacks.push(makeCallback(promise, onResolve, 'resolve'));
    this._failCallbacks.push(makeCallback(promise, onReject, 'reject'));

    return promise;
}

then方法中调用的makeCallback即上面说到的闭包函数。调用时会把 promise 对象以及相应的回调传递进去,返回一个新的函数。前一个 promise 对象持有返回函数的引用,这样在调用返回函数时,在函数内部就可以访问到 promise 对象以及回调函数了。

function makeCallback(promise, callback, action) {
    return function promiseCallback(value) {
        ...
    };
}

resolve和reject函数的实现相对简单

function resolve(promise, data) {
        if (promise._status !== 'pending') {
            return;
        }

        promise._status = 'fullfilled';
        promise._value = data;

        run(promise);
    }
function reject(promise, reason) {
        if (promise._status !== 'pending') {
            return;
        }

        promise._status = 'rejected';
        promise._value = reason;

        run(promise);
    }

function run(promise) {
        // `then`方法中也会调用,所以此处仍需做一次判断
        if (promise._status === 'pending') {
            return;
        }

        var value = promise._value;
        var callbacks = promise._status === 'fullfilled'
            ? promise._doneCallbacks
            : promise._failCallbacks;

        // Promise需要异步操作
        setTimeout(function () {
            for (var i = 0, len = callbacks.length; i < len; i++) {
                callbacks[i](value);
            }
        });

        // 每个promise只能被执行一次。虽然`_doneCallbacks`和`_failCallbacks`用户不应该直接访问,
        // 但还是可以访问到,保险起见,做清空处理。
        promise._doneCallbacks = [];
        promise._failCallbacks = [];
    }

3.2 完整实现

除上述基本方法外,文中还实现了常用静态方法如Promise.all,Promise.race,完整代码如下

/**
     * Promise对象的内部状态
     *
     * @type {Object}
     */
    var Status = {
        PENDING: 'pending',
        FULLFILLED: 'resolved',
        REJECTED: 'rejected'
    };

    function empty() {}

    /**
     * Promise构造函数
     *
     * @constructor
     * @param {Function} resolver 此Promise对象管理的任务
     */
    function Promise(resolver) {
        // ES6原生的Promise构造函数中,若不通过`new`调用Promise的构造函数,会抛出TypeError异常。此处与其一致
        if (!(this instanceof Promise)) {
            throw new TypeError('TypeError: undefined is not a promise');
        }

        // ES6原生的Promise构造函数中,若无作为函数的resolver参数,会抛出TypeError异常。此处与其一致
        if (typeof resolver !== 'function') {
            throw new TypeError('TypeError: Promise resolver undefined is not a function');
        }

        /**
         * Promise对象内部的状态,初始为`pending`。状态只能由`pending`到`fullfilled`或`rejected`
         *
         * @type {string}
         */
        this._status = Status.PENDING;

        /**
         * Promise对象resolved/rejected后拥有的data/reason
         *
         *  - 此处保存此值是为了当一个Promise对象被resolved或rejected后,继续对其调用`then`添加任务,后续处理仍能获得当前Promise的值
         *
         * @type {Mixed}
         */
        this._value;

        /**
         * 当前Promise被resolved/rejected后,需处理的任务
         *
         *  - 由于同一个Promise对象可以调用多次`then`方法,以添加多个并行任务,所以此处是一个数组
         *
         * @type {Array.<Function>}
         */
        this._doneCallbacks = [];
        this._failCallbacks = [];

        var promise = this;
        resolver(
            function (data) {
                resolve(promise, data);
            },
            function (reason) {
                reject(promise, reason);
            }
        );
    }

    Promise.prototype = {

        constructor: Promise,

        /**
         * Promise的`then`方法
         *
         * @param {Function|Mixed} onResolve 当前Promise对象被resolved后,需处理的任务
         * @param {Function|Mixed} onReject 当前Promise对象被rejected后,需处理的任务
         * @return {Promise} 返回一个新的Promise对象,用于链式操作
         */
        then: function (onResolve, onReject) {
            var promise = new Promise(empty);

            this._doneCallbacks.push(makeCallback(promise, onResolve, 'resolve'));
            this._failCallbacks.push(makeCallback(promise, onReject, 'reject'));

            // 如果在一个已经被fullfilled或rejected的promise上调用then,则需要直接执行通过then注册的回调函数
            run(this);

            return promise;
        },

        /**
         * Promise的`done`方法
         *
         * @param {Function|Mixed} onResolve 当前Promise对象被resolved后,需处理的任务
         * @return {Promise} 返回一个新的Promise对象,用于链式操作
         */
        done: function (onResolve) {
            return this.then(onResolve, null);
        },

        /**
         * Promise的`fail`方法
         *
         * @param {Function|Mixed} onReject 当前Promise对象被rejected后,需处理的任务
         * @return {Promise} 返回一个新的Promise对象,用于链式操作
         */
        fail: function (onReject) {
            return this.then(null, onReject);
        },

        /**
         * Promise的`catch`方法
         *
         * @param {Function|Mixed} onFail 当前Promise对象被rejected后,需处理的任务
         * @return {Promise} 返回一个新的Promise对象,用于链式操作
         */
        catch: function (onFail) {
            return this.then(null, onFail);
        }
    };

    /**
     * 创建一个Promise对象,并用给定值resolve它
     *
     * @param {Mixed} value 用于resolve新创建的Promise对象的值
     * @return {Promise} 返回一个新的Promise对象,用于链式操作
     */
    Promise.resolve = function (value) {
        var promise = new Promise(empty);
        resolve(promise, value);
        return promise;
    };

    /**
     * 创建一个Promise对象,并用给定值reject它
     *
     * @param {Mixed} reason 用于reject新创建的Promise对象的值
     * @return {Promise} 返回一个新的Promise对象,用于链式操作
     */
    Promise.reject = function (reason) {
        var promise = new Promise(empty);
        reject(promise, reason);
        return promise;
    };

    /**
     * 返回一个promise,这个promise在iterable中的任意一个promise被解决或拒绝后,
     * 立刻以相同的解决值被解决或以相同的拒绝原因被拒绝
     *
     * @param {Iterable.<Promise|Mixed>} iterable 一组Promise对象或其它值
     * @return {Promise} 返回一个新的Promise对象,用于链式操作
     */
    Promise.race = function (iterable) {
        if (!iterable || !iterable.hasOwnProperty('length')) {
            throw new TypeError('TypeError: Parameter `iterable` must be a iterable object');
        }

        var promise = new Promise(empty);
        for (var i = 0, len = iterable.length; i < len; i++) {
            var iterate = iterable[i];
            if (!(iterate instanceof Promise)) {
                iterate = Promise.resolve(iterate);
            }

            iterate.then(resolveRaceCallback, rejectRaceCallback);
        }

        var settled = false;

        function resolveRaceCallback(data) {
            if (settled) {
                return;
            }

            settled = true;
            resolve(promise, data);
        }

        function rejectRaceCallback(reason) {
            if (settled) {
                return;
            }

            settled = true;
            reject(promise, reason);
        }
    };

    /**
     * 返回一个promise,该promise会在iterable参数内的所有promise都被解决后被解决
     *
     * @param {Iterable.<Promise|Mixed>} iterable 一组Promise对象或其它值
     * @return {Promise} 返回一个新的Promise对象,用于链式操作
     */
    Promise.all = function (iterable) {
        if (!iterable || !iterable.hasOwnProperty('length')) {
            throw new TypeError('TypeError: Parameter `iterable` must be a iterable object');
        }

        var promise = new Promise(empty);
        var length = iterable.length;
        for (var i = 0; i < length; i++) {
            var iterate = iterable[i];
            if (!(iterate instanceof Promise)) {
                iterate = Promise.resolve(iterate);
            }

            iterate.then(makeAllCallback(iterate, i, 'resolve'), makeAllCallback(iterate, i, 'reject'));
        }

        var result = [];
        var count = 0;

        function makeAllCallback(iterate, index, action) {
            return function (value) {
                if (action === 'reject') {
                    reject(promise, value);
                    return;
                }

                result[index] = value;

                if (++count === length) {
                    resolve(promise, result);
                }
            }
        }
    };

    /**
     * 返回一个Deferred对象,包含一个新创建的Promise对象,以及`resolve`和`reject`方法
     *
     * @return {Deferred}
     */
    Promise.defer = function () {
        var promise = new Promise(empty);

        return {
            promise: promise,
            resolve: function (data) {
                resolve(promise, data);
            },
            reject: function (reason) {
                reject(promise, reason);
            }
        };
    };

    function run(promise) {
        // `then`方法中也会调用,所以此处仍需做一次判断
        if (promise._status === Status.PENDING) {
            return;
        }

        var value = promise._value;
        var callbacks = promise._status === Status.FULLFILLED
            ? promise._doneCallbacks
            : promise._failCallbacks;

        // Promise需要异步操作
        setTimeout(function () {
            for (var i = 0, len = callbacks.length; i < len; i++) {
                callbacks[i](value);
            }
        });

        // 每个promise只能被执行一次。虽然`_doneCallbacks`和`_failCallbacks`用户不应该直接访问,
        // 但还是可以访问到,保险起见,做清空处理。
        promise._doneCallbacks = [];
        promise._failCallbacks = [];
    }

    function resolve(promise, data) {
        if (promise._status !== Status.PENDING) {
            return;
        }

        promise._status = Status.FULLFILLED;
        promise._value = data;

        run(promise);
    }

    function reject(promise, reason) {
        if (promise._status !== Status.PENDING) {
            return;
        }

        promise._status = Status.REJECTED;
        promise._value = reason;

        run(promise);
    }

    function makeCallback(promise, callback, action) {
        return function promiseCallback(value) {
            // 如果传递了callback,则使用前一个promise传递过来的值作为参数调用callback,
            // 并根据callback的调用结果来处理当前promise
            if (typeof callback === 'function') {
                var x;
                try {
                    x = callback(value);
                }
                catch (e) {
                    // 如果调用callback时抛出异常,则直接用此异常对象reject当前promise
                    reject(promise, e);
                }

                // 如果callback的返回值是当前promise,为避免造成死循环,需要抛出异常
                // 根据Promise+规范,此处应抛出TypeError异常
                if (x === promise) {
                    var reason = new TypeError('TypeError: The return value could not be same with the promise');
                    reject(promise, reason);
                }
                // 如果返回值是一个Promise对象,则当返回的Promise对象被resolve/reject后,再resolve/reject当前Promise
                else if (x instanceof Promise) {
                    x.then(
                        function (data) {
                            resolve(promise, data);
                        },
                        function (reason) {
                            reject(promise, reason);
                        }
                    );
                }
                else {
                    var then;
                    (function resolveThenable(x) {
                        // 如果返回的是一个Thenable对象(此处逻辑有点坑,参照Promise+的规范实现)
                        if (x && (typeof x === 'object'|| typeof x === 'function')) {
                            try {
                                then = x.then;
                            }
                            catch (e) {
                                reject(promise, e);
                                return;
                            }

                            if (typeof then === 'function') {
                                // 调用Thenable对象的`then`方法时,传递进去的`resolvePromise`和`rejectPromise`方法(及下面的两个匿名方法)
                                // 可能会被重复调用。但Promise+规范规定这两个方法有且只能有其中的一个被调用一次,多次调用将被忽略。
                                // 此处通过`invoked`来处理重复调用
                                var invoked = false;
                                try {
                                    then.call(
                                        x,
                                        function (y) {
                                            if (invoked) {
                                                return;
                                            }
                                            invoked = true;

                                            // 避免死循环
                                            if (y === x) {
                                                throw new TypeError('TypeError: The return value could not be same with the previous thenable object');
                                            }

                                            // y仍有可能是thenable对象,递归调用
                                            resolveThenable(y);
                                        },
                                        function (e) {
                                            if (invoked) {
                                                return;
                                            }
                                            invoked = true;

                                            reject(promise, e);
                                        }
                                    );
                                }
                                catch (e) {
                                    // 如果`resolvePromise`和`rejectPromise`方法被调用后,再抛出异常,则忽略异常
                                    // 否则用异常对象reject此Promise对象
                                    if (!invoked) {
                                        reject(promise, e);
                                    }
                                }
                            }
                            else {
                                resolve(promise, x);
                            }
                        }
                        else {
                            resolve(promise, x);
                        }
                    }(x));
                }
            }
            // 如果未传递callback,直接用前一个promise传递过来的值resolve/reject当前Promise对象
            else {
                action === 'resolve'
                    ? resolve(promise, value)
                    : reject(promise, value);
            }
        };
    }

4.最后

相比,剖析 Promise 之基础篇JS Promise的实现原理文中代码结构要更加清晰,对闭包的调用更为易读。

像resolve(),reject(),run(),makeCallback()这几个工具方法,没有直接定义到Promise构造函数中,作为内部函数,产生隐式的闭包调用,而是提出来作为公共方法,通过参数传递方式将promise对象传入方法。使得代码可读性好,容易理解。

同时,在学习的过程中,读到这篇奇舞团翻译的Bluebird 是如何做到比原生实现更快的?

文中提到了Bluebird相对于原生Promise的几个优化点

  • 函数中的对象分配最小化
  • 减小对象体积
  • 可选特性懒重写

其第一点就是避免在Promise构造函数中,直接定义其他内部函数。除开前面提到的可读性问题,避免这样做,可以避免每次创建Promise对象时,同时创建全新的内部函数对象。

参考文章

如果觉得有帮助,可以扫描二维码对我打赏,谢谢

相关文章

网友评论

  • Chance722:then方法按你链接的那篇文章来看的话 返回的是一个新的promise(状态是pending) 但是试了下原生的Promise实现 then方法返回的promise如果此前是resolved的话 新返回的promise也是resolved状态 这个应该怎么看呢
  • 二木又土:这个500ms时间有可能不够,而且如果多个按钮,得写多个函数,wxml中还得写对应的语句,对于防止页面跳转,可以封装一个goto页面的函数,再里面做处理
  • LoseAnson洛施安森:有一个疑问啊 bridge promise生成时的handle函数传入的resolve是bridge promise的 还是当前的promise的

本文标题:【转向JavaScript系列】深入理解Promise

本文链接:https://www.haomeiwen.com/subject/qvxgbttx.html