好像有这么一句名言——"每一个优雅的接口,背后都有一个龌龊的实现"。最明显的例子,jQuery。之所以弄得这么复杂,因为它本来就是那复杂。虽然有些实现相对简明些,那是它们的兼容程度去不了那个地步。当然,世上总有例外,比如mootools,但暴露到我们眼前的接口,又不知到底是那个父类的东西,结构清晰但不明撩。我之所以说这样的话,因为异步列队真的很复杂,但我会尽可能让API简单易用。无new实例化,不区分实例与类方法,链式,等时髦的东西都用上。下面先奉上源码:

  1 ;(function(){
  2     var dom = this.dom = this.dom || {
  3         mix : function(target, source ,override) {
  4             var i, ride = (override === void 0) || override;
  5             for (i in source) {
  6                 if (ride || !(i in target)) {
  7                     target[i] = source[i];
  8                 }
  9             }
 10             return target;
 11         }
 12     }
 13     //
 14     //=======================异步列队模块===================================
 15     var Deferred = dom.Deferred = function (fn) {
 16         return this instanceof Deferred ? this.init(fn) : new Deferred(fn)
 17     }
 18     var A_slice = Array.prototype.slice;
 19     dom.mix(Deferred, {
 20         get:function(obj){//确保this为Deferred实例
 21             return  obj instanceof Deferred ? obj : new Deferred
 22         },
 23         ok : function (r) {//传递器
 24             return r
 25         },
 26         ng : function (e) {//传递器
 27             throw  e
 28         }
 29     });
 30     Deferred.prototype = {
 31         init:function(fn){//初始化,建立两个列队
 32             this._firing = [];
 33             this._fired = [];
 34             if(typeof fn === "function")
 35                 return this.then(fn)
 36             return this;
 37         },
 38         _add:function(okng,fn){
 39             var obj = {
 40                 ok:Deferred.ok,
 41                 ng:Deferred.ng,
 42                 arr:[]
 43             }
 44             if(typeof fn === "function")
 45                 obj[okng] = fn;
 46             this._firing.push(obj);
 47             return this;
 48         },
 49         then:function(fn){//_add的包装方法1,用于添加正向回调
 50             return  Deferred.get(this)._add("ok",fn)
 51         },
 52         once:function(fn){//_add的包装方法2,用于添加负向回调
 53             return  Deferred.get(this)._add("ng",fn)
 54         },
 55         wait:function(timeout){
 56             var self = Deferred.get(this);
 57             self._firing.push(~~timeout)
 58             return self
 59         },
 60         _fire:function(okng,args,result){
 61             var type = "ok",
 62             obj = this._firing.shift();
 63             if(obj){
 64                 this._fired.push(obj);//把执行过的回调函数包,从一个列队倒入另一个列队
 65                 var self = this;
 66                 if(typeof obj === "number"){//如果是延时操作
 67                     var timeoutID = setTimeout(function(){
 68                         self._fire(okng,self.before(args,result))
 69                     },obj)
 70                     this.onabort = function(){
 71                         clearTimeout(timeoutID );
 72                     }
 73                 }else if(obj.arr.length){//如果是并行操作
 74                     var i = 0, d;
 75                     while(d = obj.arr[i++]){
 76                         d.fire(args)
 77                     }
 78                 }else{//如果是串行操作
 79                     try{//
 80                         result = obj[okng].apply(this,args);
 81                     }catch(e){
 82                         type = "ng";
 83                         result = e;
 84                     }
 85                     this._fire(type,this.before(args,result))
 86                 }
 87             }else{//队列执行完毕,还原
 88                 (this.after || Deferred.ok)(result);
 89                 this._firing = this._fired;
 90                 this._fired = [];
 91             }
 92             return this;
 93         },
 94         fire:function(){//执行正向列队
 95             return this._fire("ok",this.before(arguments));
 96         },
 97         error:function(){//执行负向列队
 98             return this._fire("ng",this.before(arguments));
 99         },
100
101         abort:function(){//中止列队
102             (this.onabort || Deferred.ok)();
103             return this;
104         },
105         //每次执行用户回调函数前都执行此函数,返回一个数组
106         before:function(args,result){
107            return result ? result instanceof Array ? result : [result] : A_slice.call(args)
108         },
109         //并行操作,并把所有的子线程的结果作为主线程的下一个操作的参数
110         paiallel : function (fns) {
111             var self = Deferred.get(this),
112             obj = {
113                 ok:Deferred.ok,
114                 ng:Deferred.ng,
115                 arr:[]
116             },
117             count = 0,
118             values = {}
119             for(var key in fns){//参数可以是一个对象或数组
120                 if(fns.hasOwnProperty(key)){
121                     (function(key,fn){
122                         if (typeof fn == "function")
123                             fn = Deferred(fn);
124                         fn.then(function(value){
125                             values[key] = value;
126                             if(--count <= 0){
127                                 if(fns instanceof Array){//如果是数组强制转换为数组
128                                     values.length = fns.length;
129                                     values = A_slice.call(values)
130                                 }
131                                 self._fire("ok",[values])
132                             }
133                         }).once(function(e){
134                             self._fire("ng",[e])
135                         });
136                         obj.arr.push(fn);
137                         count++
138                     })(key,fns[key])
139                 }
140             }
141             self.onabort = function(){
142                 var i = 0, d;
143                 while(d = obj.arr[i++]){
144                     d.abort();
145                 }
146             }
147             self._firing.push(obj);
148             return self
149         },
150         //处理相近的迭代操作
151         loop : function (obj, fn, result) {
152             obj = {
153                 begin : obj.begin || 0,
154                 end   : (typeof obj.end == "number") ? obj.end : obj - 1,
155                 step  : obj.step  || 1,
156                 last  : false,
157                 prev  : null
158             }
159             var step = obj.step,
160             _loop = function(i,obj){
161                 if (i <= obj.end) {
162                     if ((i + step) > obj.end) {
163                         obj.last = true;
164                         obj.step = obj.end - i + 1;
165                     }
166                     obj.prev = result;
167                     result = fn.call(obj,i);
168                     Deferred.get(result).then(_loop).fire(i+step,obj);
169                 }else{
170                     return result;
171                 }
172             }
173             return (obj.begin <= obj.end) ? Deferred.get(this).then(_loop).fire(obj.begin,obj) : null;
174         }
175     }
176     //将原型方法转换为类方法
177     "loop wait then once paiallel".replace(/\w+/g, function(method){
178         Deferred[method] = Deferred.prototype[method]
179     });
180 })();

Deferred提供的接口其实不算多,then once loop wait paialle就这五个,我们可以new一个实例出来,用它的实例方法,可以直接用类名加方法名,其实里面还是new了一个实例。另外,还有两个专门用于复写的方法,before与after。before执行于每个回调函数之前,after执行于所有回调之后,相当于complete了。既然是列队,就有入队操作与出队操作,我不可能使用queue与dequeue这样土的命名。queue换成两个时间状语,then与once,相当于jQuery的done、fail,或dojo的addCallback、addErrback。dequeue则用fire与error取替,jQuery1.5的beta版也曾经用过fire,不知为何换成resolve这样难记的单词。好了,我们先不管其他API,现在就试试身手吧。

 1 var log = function (s) {
 2         window.console && window.console.log(s);
 3       }
 4       dom.Deferred(function () {
 5         log(1);//1
 6       })
 7       .then(function () {
 8         log(2);//2
 9       })
10       .then(function () {
11         log(3);//3
12       })
13       .fire();
14 //如果不使用异步列队,实现这种效果,就需要用套嵌函数
15 /*
16       var fun = function(fn){
17         fn()
18       };
19       fun(function(){
20         log(1);
21         fun(function(){
22           log(2);
23           fun(function(){
24             log(3);
25           })
26         });
27       });
28 */

当然,现在是同步操作。注意,第一个回调函数是作为构造器的参数传入,可以节约了一个then^_^。

默认如果回调函数存在返回值,它会把它作为下一个回调函数的参数传入,如:

 1 dom.Deferred(function (a) {
 2         a +=  10
 3         log(a);//11
 4         return a
 5       })
 6       .then(function (b) {
 7         b += 12
 8         log(b);//23
 9         return b
10       })
11       .then(function (c) {
12         c += 130
13         log(c);//153
14       })
15       .fire(1);

我们可以重载before函数,让它的结果不影响下一个回调函数。在多投事件中,我们也可以在before中定义,如果返回false,就中断队列了。

我们再来看它如何处理异常。dom.Deferred的负向列队与jQuery的是完全不同的,jQuery的只是正向列队的一个克隆,而在dom.Deferred中,负向列队只是用于突发情况,是配角。

 1  dom.Deferred(function () {
 2         log(1111111111)//11111111111
 3       }).
 4         then(function () {
 5         throw "error!";//发生错误
 6       }).
 7         then(function (e) {//这个回调函数不执行
 8         log(e+"222222")
 9         return 2222222
10       }).
11         once(function(e){//直到 遇上我们自定义的负向回调
12         log(e+'333333')//error!333333
13         return 333333333
14       }).
15         then(function (c) {//回到正向列队中
16         log(c)//33333333
17       }).
18         fire()

上面几个例子严格来说是同步执行,想实现异步就要用到setTimeout。当然除了setTimeout,我们还有许多方案,img.onerror script.onreadystatechange script.onload xhr.onreadystatechange self.postMessage……但它们 都有一个缺点,就是不能指定回调函数的执行时间。更何况setTimeout是没有什么兼容问题,如img.onerrot就不能用于IE6-8,postMessage虽然很快,但只支持非常新的浏览器版本。我说过,异步就是延时,延时就是等待,因此这方法叫做wait。

1 dom.Deferred(function(){
2      log(1)
3 }).wait(1000).then(function(){
4      log(2)
5 }).wait(1000).then(function(){
6      log(3)
7 }).wait(1000).then(function(){
8      log(4)
9 }).fire()

好了,我们看异步列队中最难的部分,并行操作。这相当于模拟线程了,两个不相干的东西各自做自己的事,互不干扰。当然在时间我们还是能看出先后顺序来的。担当这职责的方法为paiallel。

 1   dom.Deferred.paiallel([function(){
 2        log("司徒正美")
 3        return 11
 4     },function(i){
 5        log("上官莉绮")
 6        return 12
 7     },function(i){
 8        log("朝沐金风")
 9        return 13
10     }]).then(function(d){
11        log(d)
12     }).fire(10)

不过,上面并没有用到异步,都是同步,这时,paiallel就相当于一个map操作。

 1     var d = dom.Deferred
 2         d.paiallel([
 3           d.wait(2000).then(function(a){
 4             log("第1个子列队");
 5             return 123
 6           }),
 7           d.wait(1500).then(function(a){
 8             log("第2个子列队");
 9             return 456
10           }),
11           d.then(function(a){
12             log("第3个子列队")
13             return 789
14           })]).then(function(a){
15           log(a)
16         }).fire(3000);

最后要介绍的是loop方法,它只要改变一下就能当作animate函数使用。

1 d.loop(10, function(i){
2         log(i);
3         return d.wait(500)
4   });

添加多个列队,让它们交错进行,模拟“多线程”效果。

 1     d.loop(10, function(i){
 2         log("第一个列队的第"+i+"个操作");
 3         return d.wait(100)
 4       });
 5       d.loop(10, function(i){
 6         log("第二个列队的第"+i+"个操作");
 7         return d.wait(100)
 8       });
 9       d.loop(10, function(i){
10         log("第三个列队的第"+i+"个操作");
11         return d.wait(100)
12       });

原帖地址http://www.cnblogs.com/rubylouvre/archive/2011/03/18/1984336.html

转载于:https://www.cnblogs.com/fly-dog/p/3656347.html

javascript 异步编程二(转载 from 司徒正美)相关推荐

  1. 【学习笔记】Part1·JavaScript·深度剖析-函数式编程与 JS 异步编程、手写 Promise(二、JavaScript 异步编程)

    [学习笔记]Part1·JavaScript·深度剖析-函数式编程与 JS 异步编程.手写 Promise(课前准备) [学习笔记]Part1·JavaScript·深度剖析-函数式编程与 JS 异步 ...

  2. JavaScript异步编程原理

    众所周知,JavaScript 的执行环境是单线程的,所谓的单线程就是一次只能完成一个任务,其任务的调度方式就是排队,这就和火车站洗手间门口的等待一样,前面的那个人没有搞定,你就只能站在后面排队等着. ...

  3. JavaScript 异步编程--Generator函数、async、await

    JavaScript 异步编程–Generator函数 Generator(生成器)是ES6标准引入的新的数据类型,其最大的特点就是可以交出函数的执行的控制权,即:通过yield关键字标明需要暂停的语 ...

  4. Javascript异步编程之一异步原理

    本系列的例子主要针对node.js环境,但浏览器端的原理应该也是类似的. 本人也是Javascript新手,把自己这段时间学习积累的要点总结下来,希望可以对同样在学习Javascript/node.j ...

  5. JavaScript异步编程【下】 -- Generator、Async/await

    文章内容输出来源:拉勾教育 大前端高薪训练营 前言 在JavaScript异步编程[上]和 JavaScript异步编程[中]中,我们已经讲到了处理异步编程的两种方法:回调函数 和 Promise. ...

  6. JavaScript异步编程【上】 -- 同步和异步、事件循环(EventLoop)、微任务和宏任务、回调函数

    文章内容输出来源:拉勾教育 大前端高薪训练营 前言 在我们学习JavaScript中,我们知道,JavaScript的执行环境是单线程的.所谓单线程是指一次只能完成一个任务,如果有多个任务,就必须排队 ...

  7. JavaScript异步编程

    JavaScript异步编程 一.概述 1.单线程模型 2.同步任务和异步任务 3.任务队列和事件循环 4.异步操作的模式 回调函数 事件监听 发布/订阅 5.异步操作的流程控制 串行执行 并行执行 ...

  8. 前端JavaScript 异步编程详解

    目录 菜鸟教程官网 JavaScript 异步编程 异步的概念 详图 什么时候用异步编程 回调函数 概念 例如: 最后 菜鸟教程官网 地址 JavaScript 异步编程 异步的概念 异步(Async ...

  9. [书籍精读]《JavaScript异步编程》精读笔记分享

    写在前面 书籍介绍:本书讲述基本的异步处理技巧,包括PubSub.事件模式.Promises等,通过这些技巧,可以更好的应对大型Web应用程序的复杂性,交互快速响应的代码.理解了JavaScript的 ...

最新文章

  1. 迁移学习,让深度学习不再困难……
  2. C#中通过单例模式以及Dictionary实现键值对的映射,通过key获取value
  3. 屏蔽 iOS 系统自动更新
  4. the Open Source Community
  5. std::make_any
  6. Java EE 8发生了什么? (第2部分)
  7. Spring Boot笔记-mysql5.7使用@Table后提示doesn't exist问题
  8. Python数据分析学习笔记03:NumPy基础(数组与向量化计算)
  9. Linux系统下I/O操作讲解,深入了解实战高级I/O编程
  10. 三年期定期存款利率多少?
  11. 【百度地图API】建立全国银行位置查询系统(一)——如何创建地图
  12. 管理感悟:掌握工作的决定权
  13. 小鬼授权系统源码全解密源码 附授权代码
  14. 文本特征提取和向量化
  15. Meta-Tracker: Fast and Robust Online Adaptation for Visual Object Trackers
  16. 知我者,谓我心忧,不知我者,谓我何求。何必说?
  17. 软件项目的规模、工作量和成本是如何进行估算的
  18. mysql高并发和大流量_高并发-高并发和大流量解决方案
  19. 毕业设计 STM32天气预报盒子 - 嵌入式 单片机 物联网
  20. 【挑战30天掌握】算法与数据结构!!!

热门文章

  1. 【特征选择】过滤式特征选择法
  2. 我的世界1.12.2java下载_我的世界java版整合包
  3. 利用 Google API 调用谷歌地图 演示1
  4. 小米官网首屏纯css代码
  5. 中信期货财务因题专题报告:财务因子之单因子测试
  6. Docker的退出后进入
  7. 反向比例运算电路微分关系_20个经典模拟电路,你能记住几个?
  8. html中加减乘除符号怎么打,怎么打加减乘除的符号啊
  9. vue实现ToDoList待办事项/清单
  10. Python3.5 ‘wb’与‘w’区别以及写入excel的常见错误