Promise之Q源码解析

前言

作为E6的重要规范之一,Promise在处理异步事件已经被广泛使用,但很多人其实还是不了解Promise的原理。如果让我们自己写一个类似Promise的组件,该如何实践呢?这篇文章通过简要解析q(v1.5.0)源码库,希望给大家一点启发。

部分函数代码太长,不全部截取,只展示关键代码,非关键代码省略使用“...” 代替。

什么是Q

Q是一个功能齐全的Promise实现库,当然Q库中对Promise实现与标准的Promise有稍许的区别,但这并不妨碍我们对于q源码的解读。

Q调用示例

先定义终值调用与异常调用对应执行方法

var funcA = function(data) {
    console.warn('funcA ==============>');
    console.warn(data);
    console.warn('funcA ==============<');
    return 'A';
}

var funcB = function(data) {
    console.warn('funcB ==============>');
    console.warn(data);
    console.warn('funcB ==============<');
    return 'B';
}

var err = function(e) {
    console.warn('Catch err ==============>');
    console.warn(e);
    console.warn('Catch err ================<');
}

传入对象构造一个Promise:

new Q({
    then: function(resolve, reject) {
        resolve('data');
    }
})
    .then(funcA)
    // then方法中可以传入catch错误执行方法
    .then(funcB, err)
    .catch(err);

直接传入数据构造Promise:

new Q('data')
    .then(funcA)
    // then方法中可以传入catch错误执行方法
    .then(funcB, err)
    .catch(err);

入口构造函数

function Q(value) {
    // ...
    // 在这个方法里面有一个“isPromiseAlike”方法用于判断用户开始构造“Promise”时传入的是否是一个对象,
    // 然后在分开调用“coerce”与“fulfill”方法
    if (isPromiseAlike(value)) {
        return coerce(value);
    } else {
        return fulfill(value);
    }
}
coerce 与 fulfill 有什么区别

先看 coerce 中的关键代码

function coerce(promise) {
    // ...
    // 这里的关键点是调用了deferred.resolve方法,至于deferr是什么后文中还会进行讲解
    promise.then(deferred.resolve, deferred.reject, deferred.notify);
    // ...
}

deferred.resolve 里面比较简单,其实就是生成了一个新的Q Promise。

deferred.resolve = function (value) {
    // ...
    become(Q(value));
};

所以:殊途同归,coerce 方法其实就是将传入对象的构造方法转换为传入数据的构造方法,最后调用的都是“fulfill”,所以只需要分析“fulfill”方法即可。

fulfill 里面干了什么
function fulfill(value) {
    return Promise({
        "when": function () {
            return value;
        },
        "get": function (name) {
            return value[name];
        },
        // ... 除了when与when,还包含set、delete、post、apply、keys方法
    }, void 0, function inspect() {
        return { state: "fulfilled", value: value };
    });
}

很简单,fulfill 里面返回了一个自定义的Promise,后文会讲到Promise模块,此处不做分析。

Q的初始构造分析到此结束,下文分析其中的关键代码

Promise

/** 
 * @param descriptor 包含对value的处理方法的对象(Q(value),构造函数中的value值),这个对象中包含的方法包括when、get、set、post、delete中的其中一个或者全部
 * @param fallback 异常处理方法
 * @param inspect 获取Promise执行状态的方法,有4种状态 unknow、pending、fulfilled、rejected
 */
function Promise(descriptor, fallback, inspect) {
    // ...
    
    // 从Promise原型链创建一个新的Promise对象,其中的原型链中就包含了then与catch方法
    var promise = object_create(Promise.prototype);
    
    // 定义promise的触发事件
    // 参数resolve是终值执行传入的值,比如上文中的funcA、funcB
    // 参数op,是descriptor中的定义的其中一个方法名,when、get等
    promise.promiseDispatch = function (resolve, op, args) {
        var result;
        try {
            if (descriptor[op]) {
                result = descriptor[op].apply(promise, args);
            } else {
                // catch时调用
                // 参数名虽然叫promise,但其实是一个Error类型的数,这里容易让人引起误解
                result = fallback.call(promise, op, args);
            }
        } catch (exception) {
            result = reject(exception);
        }
        if (resolve) {
            resolve(result);
        }
    };
    
    // 后续通过inspect方法获取Promise执行状态
    promise.inspect = inspect;

    return promise;
}

1、Promise中添加 promiseDispatch后续可以方便后续随时触发Promise中的then与catch方法回调。
2、Promise后续通过inspect方法获取Promise执行状态
3、Promise这个方法本身是一个构造方法,在这个构造方法中有返回了Promise自己的一个实例,起原型链也指向了Promise中的一大堆方法,达到then链式调用的效果。是不是与jQuery中的链式调用异曲同工。

defer

function defer() {
    // 参数messages用来存储progressListeners 对应方法的参数
    // 参数progressListeners 用来存储promise被resolve方法执行之前的所有回调方法
    // 参数resolvedPromise 表示已经执行完毕的Promise对象
    var messages = [], progressListeners = [], resolvedPromise;

    var deferred = object_create(defer.prototype);
    var promise = object_create(Promise.prototype);

    // 重写Promise中的触发函数
    // 在触发函数中填充message 与 progressListeners 供后续调用。
    promise.promiseDispatch = function (resolve, op, operands) {
        var args = array_slice(arguments);
        if (messages) {
            messages.push(args);
            if (op === "when" && operands[1]) {
                progressListeners.push(operands[1]);
            }
        } else {
            Q.nextTick(function () {
                resolvedPromise.promiseDispatch.apply(resolvedPromise, args);
            });
        }
    };
    
    // 定义promise获取状态的方法,添加了pending“未执行”状态
    promise.inspect = function () {
        if (!resolvedPromise) {
            return { state: "pending" };
        }
        return resolvedPromise.inspect();
    };

    function become(newPromise) {
        // 已执行Promise赋值
        resolvedPromise = newPromise;
    
        // 依次执行then中的回调方法
        // 为什么需要一次执行,因为then方法中除了resolve,还可能包括reject与progressed回调
        // Prototype.protytype.then中三个参数resolve、reject、progressed
        array_reduce(messages, function (undefined, message) {
            Q.nextTick(function () {
                newPromise.promiseDispatch.apply(newPromise, message);
            });
        }, void 0);

        messages = void 0;
        progressListeners = void 0;
    }
    
    deferred.promise = promise;
    
    // Promise终值执行
    deferred.resolve = function (value) {
        if (resolvedPromise) {
            return;
        }

        become(Q(value));
    };

    // ...
    
    // 拒绝态方法
    deferred.reject = function (reason) {
        if (resolvedPromise) {
            return;
        }

        become(reject(reason));
    };
    
    // ...

    return deferred;
}

deffer可以理解为一个观察者,观察者模式又叫做发布订阅模式,它定义了一种一对多的关系,让多个观察者对象同时监听某一个主题对象,这个主题对象的状态发生改变时就会通知所有观察着对象。 它是由两类对象组成,主题和被观察者,主题负责发布事件,同时观察者通过订阅这些事件来观察该主体,发布者和订阅者是完全解耦的,彼此不知道对方的存在,两者仅仅共享一个自定义事件的名称。deffer负责:
1、在deffer中创建一个Promise(主题)
2、对deffer中Promise的操作必须通过deffer中的相对应方法来进行相关操作(观察者)
3、当promise没有被resolve之前,所有回调函数与回调函数相关的参数会存在“progressListeners”与“messages”数组中。(通过promiseDispatch)
4、当Promise被resolve之后,立即执行之前存储的所有回调函数,当回调函数全部执行完毕之后,Promise将根据“resolvedPromise”来区分状态。

nextTick

Q.nextTick(function () {
    // ...
});

在源码中多次会有Q.nextTick出现,Q.nextTick中主要处理什么逻辑呢。这个函数其实是专门针对node做的一步异常处理。
由于node的回调异步特性,无法通过try catch来捕捉所有的异常,nextTick方法中主要使用dommain对捕获的异常做出处理。本文不做发散解读

then的处理

/**
 * @param {Function} fulfilled 终值执行方法
 * @param {Function} rejected 错误拒绝方法
 * @param {Function} progressed 处理种植执行方法中返回的数据执行方法,可以理解为二次包装
 * @returns {deffer.promise}
 */
Promise.prototype.then = function (fulfilled, rejected, progressed) {
    var self = this; 
    var deferred = defer();    // 观察者在这里已经排上了用场
    var done = false;   // 终值执行方法是否已执行
    
    function _fulfilled(value) {
        try {
            return typeof fulfilled === "function" ? fulfilled(value) : value;
        } catch (exception) {
            return reject(exception);
        }
    }

    function _rejected(exception) {
        if (typeof rejected === "function") {
            makeStackTraceLong(exception, self);
            try {
                return rejected(exception);
            } catch (newException) {
                return reject(newException);
            }
        }
        return reject(exception);
    }

    function _progressed(value) {
        return typeof progressed === "function" ? progressed(value) : value;
    }

    Q.nextTick(function () {
        self.promiseDispatch(function (value) {
            if (done) {
                return;
            }
            done = true;

            deferred.resolve(_fulfilled(value));
        }, "when", [function (exception) {
            if (done) {
                return;
            }
            done = true;
            
            deferred.resolve(_rejected(exception));
        }]);
    });

    // progress 通知处理
    self.promiseDispatch(void 0, "when", [void 0, function (value) {
        var newValue;
        var threw = false;
        try {
            newValue = _progressed(value);
        } catch (e) {
            threw = true;
            if (Q.onerror) {
                Q.onerror(e);
            } else {
                throw e;
            }
        }

        if (!threw) {
            deferred.notify(newValue);
        }
    }]);

    return deferred.promise;
};

then方法比较简单,主要是一些执行函数封装处理与观察者deffer通知管理。
1、依次二次封装处理then中传入的fulfilled, rejected, progressed方法
2、在适当的时候通知观察者deffer执行resolve操作
3、最后返回观察者对象中的Promise,可以达到链式调用效果

异常处理

catch方法
Promise.prototype["catch"] = function (rejected) {
    return this.then(void 0, rejected);
};

代码库中并未对catch进行单独意义上的处理,catch方法只是简单的转发到then中而已,唯一的区别是,catch中不传入终值执行态。

then中关于拒绝态度rejected的处理
function _rejected(exception) {
    // 在then方法中存在传入拒绝态执行方法的情况下,调用then中的rejected方法,这样Promise还会依次调用后续的then往下走
    if (typeof rejected === "function") {
        // 错误trance日志处理
        makeStackTraceLong(exception, self);
        try {
            // rejected 是我们自己在then中传入的拒绝态度处理方法
            return rejected(exception);
        } catch (newException) {
            return reject(newException);
        }
    }
    // reject是catch传入的拒绝太通用处理方法
    return reject(exception);
}

在_rejected中处理了通用的catch,也进行了then中单独传入拒绝态方法的处理。(注意单词rejected与reject)

reject方法中做了什么
function reject(reason) {
    var rejection = Promise({
        "when": function (rejected) {
            // ...
            return rejected ? rejected(reason) : this;
        }
    }, function fallback() {
        return this;
    }, function inspect() {
        return { state: "rejected", reason: reason };
    });
    
    // ...

    return rejection;
}

调用了Promise的构造方法,与Q初始化构造调用Promise构造方法的区别是获取Promise方法返回的状态是rejected。
是不是同样的味道,熟悉的感觉


对Q的简单分析到此结束,除去then、catch,Q的代码库中还提供了all、any、finally等方法,有兴趣的可以研究研究,有点小复杂

参考资料

Promise规范
观察者模式
关于jQuery原型链调用
Q Api
Q design
Node domain
stack traces

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值