前言
作为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