博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
javascript 异步编程二(转载 from 司徒正美)
阅读量:6194 次
发布时间:2019-06-21

本文共 10910 字,大约阅读时间需要 36 分钟。

好像有这么一句名言——"每一个优雅的接口,背后都有一个龌龊的实现"。最明显的例子,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 self149 },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 : null158 }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);//312       })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 b10       })11       .then(function (c) {12         c += 13013         log(c);//15314       })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 222222210 }).11 once(function(e){
//直到 遇上我们自定义的负向回调12 log(e+'333333')//error!33333313 return 33333333314 }).15 then(function (c) {
//回到正向列队中16 log(c)//3333333317 }).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 1310     }]).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 45610           }),11           d.then(function(a){12             log("第3个子列队")13             return 78914           })]).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

你可能感兴趣的文章
一句话,讲清楚java泛型的本质(非类型擦除)
查看>>
百度联合清华发布国内首个基于AI实践的产业智能化白皮书
查看>>
我的友情链接
查看>>
博文第一篇《明志》
查看>>
java Stack类 Vector类
查看>>
Go test 命令工作原理
查看>>
Dynamips结合VMware搭建站点到站点×××环境
查看>>
写Java程序的三十个基本规则
查看>>
我的友情链接
查看>>
004 查看表结构命令
查看>>
Exchange 2016 CU9 已发布
查看>>
java jackson json序列化
查看>>
CP(1)
查看>>
redhat7.2升级openssl、openssh
查看>>
Gson自动解析json
查看>>
[备忘]如何接收向shell脚本传入的参数
查看>>
Wine里的中文程序出现方块字的解决方法以及Wine快捷方式的命令格式
查看>>
xendesktop配置DDC连接vcenter,添加vcenter证书步骤。
查看>>
12月流量入口占比动态:搜索引擎季军 份额破20%
查看>>
Citrix XenDesktop虚拟化桌面定期重启命令
查看>>