// $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);
};
Javascript异步流程控制之Promise(2)-Angular $q源码解读
原文写于 2015-01-27
接上一篇 Angular $q简介 ,这一篇我们就Angular对$q的代码实现来学习下Promise的实现原理及思想
上篇讲到了Angular Promise的基本API,其中就典型的应用类似这样
Promise.then
首先我们要明确一个概念就是js是单线程语言,一个时间只能有一个线程在执行。那么上面的执行流程自然就是: 启动定时器并往事件队列里加入一个回调函数 ---> 打印1 ---> 调用defer.promise.then方法 ---> 打印2 --->主线程空闲开始等5秒(实际会小于5s)执行回调 ---> 调用defer.resolve ---> 打印10
至于这中间究竟发生了上面,先来看看angular是怎样实现这两个方法的
Promise.then
Promise构造函数原型上维护一个方法 then ,当一个promise对象调用该方法时会往自己的成员属性 $$state 上构建一个 pending数组,然后将生成的deferred跟成功、失败回调压入pending数组。
拿上文的案例来看, defer.promise.then之后 defer.promise.$$state.pending = [[new Deferred(), function (a){console.log(a)}]];
再来看看defer.resolve发生了什么
Deferred.resolve
核心方法是 Deferred.$$resolve , 上文中 defer.resolve(10) 之后会走到 scheduleProcessQueue(this.promise.$$state);
nextTick方法定义在这里
再往上看谁调用了qFactory
没错就是我们使用的 $q 服务,也就是$q在初始化的时候会 生成
nextTick = function(callback) {$browser.defer(callback);}
$browser.defer干了什么事?
没错,很简单,就是一个定时器。将定时器时间默认设置为0的意图很明显,使当前任务脱离主线程,在事件队列执行时再做处理,也就是所谓的defer延时
回到 scheduleProcessQueue,被手动从主线程中移除的任务是
nextTick(function() { processQueue(state); });
看看 processQueue 是干嘛的
没错,这个就是Promise处理最核心的部位。当主线程执行完毕开始处理事件队列时,processQueue 开始执行, 它会将 state.pending 队列(没错,队列里的内容就是我们调用then()方法时一个个压入的)遍历然后依次调用
假如有这样一段代码
promiseO.then(funcA).then(funcB);
那么执行后会发生这样一个变化
then的核心思想是它会往当前promise的pending队列中压入then链下一个promise对象的Deferred(promise=Deferred.promise),然后通过这种节点关系构成整个then链
介绍完promise.then我们再来介绍一下$q.all([promises]).then(funcT),来看看他是怎么实现当所有promise被resolve之后再走向下一步的吧
$q.all
很简单,forEach promises列表之后,在每个promise的then链加一个回调,关键代码在这一行
if (!(--counter)) deferred.resolve(results);
每一个promise成功执行了then回调之后会--counter,如果某个promise是最后一个被resolve的则将所有的results resolve,这个时候他的then链就开始执行了
$q.when
$q.when(val/promiseLikeObj)的用处很简单,就是将一个简单对象/或promiseLike的对象包装成$q信任的promise对象并将值加入到then链中。
写在最后:
至此$q的几个基本api都基于源码做了简单介绍,个人在研究源码的过程中最大的收获就是对js的异步模型有了更深刻的理解,并对工程化应用有了感官的认识,强烈建议有兴趣的同学读读源码。Promise是一种异步编程模型,是当前被普遍认可的一种解决方案,目前业界还有一些其他的异步解决方案诸如 thunk、co 模型等,思路各有差异跟优缺点,有兴趣的同学可以去了解相信会对你理解js的异步模型有进一步认识。
PS:
下一篇会介绍一下ES6中的Promise的api用法,敬请期待