Javascript异步流程控制之Promise(2)-Angular $q源码解读
原文写于 2015-01-27 https://github.com/kuitos/kuitos.github.io/issues/16
接上一篇 Angular
q简介,这一篇我们就Angular对
q的代码实现来学习下Promise的实现原理及思想
上篇讲到了Angular Promise的基本API,其中就典型的应用类似这样
Promise.then
var defer = $q.defer();
setTimeout(function (){
defer.resolve(10);
},5000);
console.log("1");
defer.promise.then(function(a){
console.log(a);
});
console.log("2");
// 打印的顺序是1,2,10
// 当我们依赖的数据会在一个异步的时间点返回时,我们需要使用resolve 跟 then 来配合
首先我们要明确一个概念就是js是单线程语言,一个时间只能有一个线程在执行。那么上面的执行流程自然就是: 启动定时器并往事件队列里加入一个回调函数 —> 打印1 —> 调用defer.promise.then方法 —> 打印2 —>主线程空闲开始等5秒(实际会小于5s)执行回调 —> 调用defer.resolve —> 打印10
至于这中间究竟发生了上面,先来看看angular是怎样实现这两个方法的
Promise.then
“`js
// Promise.then
function Promise() {
this.$$state = { status: 0 };
}Promise.prototype = {
then: function(onFulfilled, onRejected, progressBack) {
var result = new Deferred();this.$$state.pending = this.$$state.pending || []; this.$$state.pending.push([result, onFulfilled, onRejected, progressBack]); if (this.$$state.status > 0) scheduleProcessQueue(this.$$state); return result.promise; },
………
“`
Promise构造函数原型上维护一个方法 then ,当一个promise对象调用该方法时会往自己的成员属性
再来看看defer.resolve发生了什么
Deferred.resolve
// Deferred.resolve Deferred.prototype = { resolve: function(val) { if (this.promise.$$state.status) return; if (val === this.promise) { this.$$reject($qMinErr( 'qcycle', "Expected promise to be resolved with value other than itself '{0}'", val)); } else { this.$$resolve(val); } }, $$resolve: function(val) { var then, fns; fns = callOnce(this, this.$$resolve, this.$$reject); try { if ((isObject(val) || isFunction(val))) then = val && val.then; if (isFunction(then)) { this.promise.$$state.status = -1; then.call(val, fns[0], fns[1], this.notify); } else { this.promise.$$state.value = val; this.promise.$$state.status = 1; scheduleProcessQueue(this.promise.$$state); } } catch (e) { fns[1](e); exceptionHandler(e); } }, ...... ``` 核心方法是 Deferred.$$resolve , 上文中 defer.resolve(10) 之后会走到 scheduleProcessQueue(this.promise.$$state); ```js // scheduleProcessQueue function scheduleProcessQueue(state) { if (state.processScheduled || !state.pending) return; state.processScheduled = true; nextTick(function() { processQueue(state); }); }
nextTick方法定义在这里
// nextTick function qFactory(nextTick, exceptionHandler) { var $qMinErr = minErr('$q', TypeError); .....
再往上看谁调用了qFactory
// qFactory function $$QProvider() { this.$get = ['$browser', '$exceptionHandler', function($browser, $exceptionHandler) { return qFactory(function(callback) { $browser.defer(callback); }, $exceptionHandler); }]; }
没错就是我们使用的 q服务,也就是 q在初始化的时候会 生成
nextTick = function(callback) {$browser.defer(callback);}
$browser.defer干了什么事?// $browser.defer self.defer = function(fn, delay) { var timeoutId; outstandingRequestCount++; timeoutId = setTimeout(function() { delete pendingDeferIds[timeoutId]; completeOutstandingRequest(fn); }, delay || 0); pendingDeferIds[timeoutId] = true; return timeoutId; };
没错,很简单,就是一个定时器。将定时器时间默认设置为0的意图很明显,使当前任务脱离主线程,在事件队列执行时再做处理,也就是所谓的defer延时
回到 scheduleProcessQueue,被手动从主线程中移除的任务是nextTick(function() { processQueue(state); });
看看 processQueue 是干嘛的// processQueue function processQueue(state) { var fn, promise, pending; pending = state.pending; state.processScheduled = false; state.pending = undefined; for (var i = 0, ii = pending.length; i < ii; ++i) { promise = pending[i][0]; fn = pending[i][state.status]; try { if (isFunction(fn)) { promise.resolve(fn(state.value)); } else if (state.status === 1) { promise.resolve(state.value); } else { promise.reject(state.value); } } catch (e) { promise.reject(e); exceptionHandler(e); } } }
没错,这个就是Promise处理最核心的部位。当主线程执行完毕开始处理事件队列时,processQueue 开始执行, 它会将 state.pending 队列(没错,队列里的内容就是我们调用then()方法时一个个压入的)遍历然后依次调用
假如有这样一段代码
promiseO.then(funcA).then(funcB);
那么执行后会发生这样一个变化promiseO.then(funcA) ---> promiseO.$$state.pengding = [[deferredA, funcA]]; return deferredA.promise; deferredA.promise.then(funcB) ---> deferredA.promise.$$state.pending = [[deferredB, funcB]]; return deferredB.promise; // 当各个promise被依次resolve了 // 1. 首先是promiseO.resolve(10) ---> promiseO.$$state.value = 10;promiseO.$$state.status = 1;scheduleProcessQueue(promiseO.$$state); // 2. scheduleProcessQueue最后走到processQueue方法,会执行promiseO.$$state.pengding队列,这时候会执行 deferredA.resolve(funcA(promiseO.$$state.value)) // 3. 假设funcA(promiseO.$$state.value)返回20,那么就是 deferredA.resolve(20)。这时候就又回到了步骤1 // 依次执行后then链上所有方法均会执行
then的核心思想是它会往当前promise的pending队列中压入then链下一个promise对象的Deferred(promise=Deferred.promise),然后通过这种节点关系构成整个then链
介绍完promise.then我们再来介绍一下$q.all([promises]).then(funcT),来看看他是怎么实现当所有promise被resolve之后再走向下一步的吧
$q.all
// $q.all function all(promises) { var deferred = new Deferred(), counter = 0, results = isArray(promises) ? [] : {}; forEach(promises, function(promise, key) { counter++; when(promise).then(function(value) { if (results.hasOwnProperty(key)) return; results[key] = value; if (!(--counter)) deferred.resolve(results); }, function(reason) { if (results.hasOwnProperty(key)) return; deferred.reject(reason); }); }); if (counter === 0) { deferred.resolve(results); } return deferred.promise; }
很简单,forEach promises列表之后,在每个promise的then链加一个回调,关键代码在这一行
if (!(--counter)) deferred.resolve(results);
每一个promise成功执行了then回调之后会–counter,如果某个promise是最后一个被resolve的则将所有的results resolve,这个时候他的then链就开始执行了$q.when
//$q.when var when = function(value, callback, errback, progressBack) { var result = new Deferred(); result.resolve(value); return result.promise.then(callback, errback, progressBack); };
q.when(val/promiseLikeObj)的用处很简单,就是将一个简单对象/或promiseLike的对象包装成 q信任的promise对象并将值加入到then链中。
写在最后:
至此$q的几个基本api都基于源码做了简单介绍,个人在研究源码的过程中最大的收获就是对js的异步模型有了更深刻的理解,并对工程化应用有了感官的认识,强烈建议有兴趣的同学读读源码。Promise是一种异步编程模型,是当前被普遍认可的一种解决方案,目前业界还有一些其他的异步解决方案诸如 thunk、co 模型等,思路各有差异跟优缺点,有兴趣的同学可以去了解相信会对你理解js的异步模型有进一步认识。
PS:
下一篇会介绍一下ES6中的Promise的api用法,敬请期待