第二十九课:javascript异步处理
大家知道javascript中有多少方法能够实现异步处理吗?setTimeout(),setInterval()是最常用的两个。XMLHttpRequest对象,进行ajax请求时。postMessage()进行跨域操作时。WebWorker创建新的线程时。setImmediate方法(新的setTimeout方法)。requestAnimationFrame进行动画操作时。这些东西都有一个共同的特点,就是拥有一个回调函数。有的异步API还提供了相对应的中断API,比如:clearTimeout,clearInterval,clearImmediate,cancelAnimationFrame。
早些年,我们就是通过setTimeout和setInterval在网页上实现动画的,这种动画其实就是通过异步API不断的调用同一个回调方法实现的,回调方法里面对元素节点的某些样式进行很小范围的改动。
首先,我们来讲一下setTimeout和setInterval这两个API,这里只讲它不常见的知识点:
(1)它们的回调方法,如果执行时间大于间隔时间(比如:setInterval(function(){里面执行代码的事件大于50毫秒},50)),那么实际上的间隔时间会大于50毫秒(因为js执行线程其实是一个队列,它执行完一个函数后,才会执行另外一个函数,因此,这段代码的意思是:每隔50毫秒,给执行线程添加一个函数,如果执行线程在执行这个函数时,后面又来了一个函数,后面这个函数会在那里排队,要等前面那个函数执行完成,它才会执行,因此实际上函数执行的间隔时间大于50毫秒)。
(2)它们存在一个最小的时钟间隔,IE6-8下为15.6ms,IE9为10ms,IE10和其他标准浏览器为4ms。意思就是说,如果你setInterval(functiojn(){},1),这个代码的意思是一毫秒就执行一次function,但是实际上,浏览器它们有一个最小的间隔,即便你写了1ms,它也会按照它的这个最小事件间隔来执行function(比如:IE6-8会15.6毫秒才执行一次function)。如果你觉得IE6-8下,最短时钟间隔太大,你可以利用image死链时立即执行onerror回调的情况进行改造,比如:
var orig_setTimeout = window.setTimeout;
window.setTimeout = function(callback,time){
if(time > 15) {
orig_setTimeout(callback,time);
}
else{ //当间隔时间小于15毫秒时,就新建一个Image对象,给它一个错误的src,这时会立即调用onerror回调方法,这时就会执行callback,实现间隔时间小于15毫秒的功能。
var img = new Image();
img.onload = img.onerror = function(){
callback();
};
img.src = "data:,foo";
}
}
(3)不写第二个参数时,浏览器自动分配时间,IE,Firefox中,第一个分配可能给个100ms,往后会慢慢缩小到最小时钟间隔。Safari,Chrome,Opera则分配一个10ms。Firefox中,setInterval不写第二个参数,会当做setTimeout处理,只执行一次。
(4)IE10+和标准浏览器支持额外参数,从第三个参数起,作为回调的传参传入。比如:setTimeout(function(){},1000,1,2,4),那么function中的[].slice.call(arguments) = [1,2,4]。IE6-9可以这样模拟:
if(IE9-){
(function(overrideFun){
window.setTimeout = overrideFun(window.setTimeout);
window.setInterval = overrideFun(window.setInterval);
})
(
function(originalFun){
return function(callback, delay){
var args = [].slice.call(arguments,2); //从第三个参数开始取
return originalFun(function(){
if(typeof callback == "string"){ //如果第一次参数传入的是字符串
eval(callback);
}else{
callback.apply(this,args); //把参数传入回调方法
}
},delay);
};
}
)
}
(5)setTimeout方法的事件参数若为负数或0或极大的正数,标准浏览器都立即执行,而老版本的IE处理会出现较大的差异,不用研究。
接下来,我们来讲解下Deferred对象(jQuery中的ajax异步处理对象)的前身Mochikit Deferred。
Deferred是当今最著名的异步模型,它原来是Python的Twisted框架的一个类,后来被Mochikit框架引进来,后面被dojo抄去,jQuery后面也引进。我们来详细讲一下Mochikit Deferred的实现原理(接下来的Deferred指的是Mochikit Deferred):
Deferred内部把回调分成两种,一种是成功回调,用于正常时执行,一种叫错误回调,用于出错时执行。各自组成两个队列,我们可以叫做成功队列与错误队列。在添加回调时,它是一组一组(成功回调和错误回调)的添加的,每组的回调只会执行一个(不是执行成功回调,就是执行错误回调),每组回调接收到的参数,都是上一组回调处理后返回的结果,只有第一次组的回调接收的参数是用户传入的。那么如何决定是执行成功回调还是错误回调呢,也是根据上一组的结果决定的,如果上一组的结果抛出错误,那么这一组就会执行错误回调,如果这一组的错误回调不抛出错误,那么下一组的回调就执行成功回调。第一组的执行是用户决定的,意思就是用户调用成功回调的方法,就执行成功回调,用户执行错误回调的方法就执行错误回调。
我们先来看一下Deferred里面的方法:
addCallback 添加成功回调的方法。
addErrback 添加错误回调的方法
addBoth 同时添加正常回调和错误回调的方法
这三个方法内部都会调用addCallbacks方法,而这个方法的参数只能是两个函数或一个函数一个null。也就是说上面的三个方法会把参数转换成两个函数或一个函数一个null,然后传给addCallbacks方法。Deferred实例有一个chain数组属性,数组中的每一项都是一个双元素的数组,比如:
deferred.chain = [ [callback,errorcallback], [callback1,errorcallback1], [callback2,errorcallback2] ];
举个例子:
var d = new Deferred();
d.addCallback(myCallback);
d.addErrback(myErrback);
d.addBoth(myBoth);
d.addCallbacks(myCallback,myErrback);
这时,d.chain = [ [myCallback, null], [null, myErrback], [myBoth, myBoth], [myCallback, myErrback] ];
触发这些回调是通过调用d.callback和d.errback方法实现的。这两个方法里面的流程是一致的,首先检查此Deferred对象有没有被调用过,如果没有,就调用_resback方法。
当然用户可以在callback或errback中传入参数,传入的参数在_resback方法中会生成一个数组,如果调用的是callback方法(成功的回调),那么就把参数放到数组的第一个位置,如果调用的是errback方法(失败的回调),那么就把参数放到数组的第二个位置。然后_resback方法会判断执行有没有被切断(异步过程有没有被终止),没有的话,就调用_fire方法执行回调。
_fire方法就是不断弹出chain数组中的一组函数,根据状态取第一个回调还是第二个回调(第一个是成功回调,第二个是失败回调)执行,每一组回调都接收上一组回调的返回值作为参数。举个例子:
function increment(value){
console.log(value);
return value+1;
}
var d = new Deferred();
d.addCallback(increment); //d.chain = [[increment,null]]
d.addCallback(increment); //d.chain = [[increment,null],[increment,null]]
d.addCallback(increment); //d.chain = [[increment,null],[increment,null],[increment,null]]
d.callback(1); //_resback(1)-> [1,null] -> fire([1,null]) -> 循环取出d.chain中的每一组函数, 因为传入的数组第一项是1,就代表成功回调,因此执行第一组函数的成功回调increment方法,这时打印出1,返回2,而这个2会继续传给第二组函数,因为也是成功回调,所以就执行第二组函数的成功回调increment方法,打印出2,返回3。以此类推。
那么失败回调什么时候执行呢,在以上的执行流程中,会有一个try catch,如果回调方法抛出错误,就会catch住,然后执行下一组函数中的失败回调。举个例子:
var d = new Deferred();
d.addCallback(function(a){ console.log(a);return 4}).addBoth(function(a){console.log(a);throw "抛错"},function(b){console.log(b);return "xx"}).addBoth(function(a){console.log(a); return "正常"},function(b){console.log(b);return "出错"}).addBoth(function(a){console.log(a + "正常")},function(b){console.log(b+"继续出错")})
d.callback(3);
因为调用的是callback方法所以是成功回调,因此打印出a(也就是3),返回4给下一组函数,下一组函数收到这个4,因为没有抛出错误,所以是成功回调,因此打印出4,throw "抛错",这时下一组函数,就会执行失败回调,也就是function(b){console.log(b);return "出错"},打印出Error:抛错,返回"出错",这时没有抛出错误,而是正常返回"出错"这个字符串,因此下一组函数,就会执行成功回调,也就是function(a){console.log(a + "正常")},这时打印出"出错正常"。
在Mochikit中,Deferred还有一个重要的功能就是,可以并归多个Ajax的请求结果,然后再做处理。它是使用DeferredList实现的。早期的jQuery和Prototype没有这个东西,它们之前是使用计数器实现的,非常复杂。举个例子:
有一个业务,需要发起4个Ajax请求,这4个请求的地址和返回时间不一样,必须等到它们都处理完成后,整合它们4个的返回数据,然后根据这个整合的数据再发起两个ajax请求,等到这两个ajax请求全部处理完成,整合这两个请求的数据后,再进行一次ajax请求,返回数据才算一次业务处理成功。如果你不使用异步对象来处理,你可以想想你的代码会写的多复杂,而且多容易出错。如果使用DeferredList来实现,非常简单,先把那4个ajax请求放到DefrredList对象d1中,然后4个ajax全部处理完成后,才会触发DefrredList对象的回调,而在这个回调中把这4个请求的数据整合,然后根据整合的数据发起两个ajax请求,这两个ajax请求又放到一个DefrredList对象d2中,等这两个ajax请求全部处理完成后,就会执行d2的回调方法,最后在这个回调中整合这2个请求的数据,发送最后一次ajax请求,就OK了。
下一课,将讲解JSDeferred对象,它基本奠定了后来称为Promise/A的范式。
加油!
第二十九课:javascript异步处理的更多相关文章
- NeHe OpenGL教程 第二十九课:Blt函数
转自[翻译]NeHe OpenGL 教程 前言 声明,此 NeHe OpenGL教程系列文章由51博客yarin翻译(2010-08-19),本博客为转载并稍加整理与修改.对NeHe的OpenGL管线 ...
- python第二十九课——文件读写(复制文件)
自定义函数:实现文件复制操作有形参(2个) 没有返回值相似版(不用) def copyFile(src,dest): #1.打开两个文件:1个关联读操作,1个关联写操作 fr=open(src,'rb ...
- python第二十九课——文件读写(读取读取中文字符)
演示:读取中文字符 结论: 1).如果不设置encoding,默认使用gbk进行编解码 2).如果编码和解码不一致,最终导致报错,但是一旦设置了errors='ingore',那么就不会报错,而采取乱 ...
- JAVA学习第二十九课(经常使用对象API)- String类
多线程告一段落,開始经常使用对象API的涉及,背也要背下来!.! 日后开发,遇见最多的对象是文字,也就是字符串 String类 字符串是一个特殊对象 字符串一旦初始化就不能够被改变 一.特点 publ ...
- 【批处理学习笔记】第二十九课:ASCII码
前面的例子中,我们已经使用过一次ASCII码了,也就是那个笑脸.ASCII码是图形化的符号,可以用来点缀我们的批处理的. 在cmd窗口中我们可以通过任意一个字符的ASCII码来输入该字符,比如C ...
- 潭州课堂25班:Ph201805201 django 项目 第二十九课 docker实例,文件下载前后台实现 (课堂笔记)
docker 实例 :wq!保存退出 放入一个 html 文件 权限不够,加 sudo 查看本地仓库的 image 运行 docker -- name,后跟个运行名, -p 物理机端口映射到容器端口, ...
- python第二十九课——文件读写(写数据的操作)
演示写数据的操作: 结论:往文件中写入数据,如果文件不存在,先创建文件,再写入内容 #1.打开文件 fw=open(r'd.txt','w',encoding='utf-8') #2.写数据操作 fw ...
- python第二十九课——文件读写(readline()和readlines()的使用)
演示readline()和readlines()的使用: #1.打开文件 f3=open(r'a.txt','r',encoding='gbk') #2.读取数据 content3=f3.readli ...
- python第二十九课——文件读写(读取数据操作)
演示读取数据操作:path=r'a.txt' 1.打开文件f1=open(path,'r') 2.读取数据content1=f1.read(3)print(content1) content1=f1. ...
随机推荐
- centos 下使用locate命令
首先安装mlocate yum -y install mlocate 更新数据库:updatedb 查找:locate nginx
- CI 框架中的自定义路由规则
在 CI 框架中,一个 URL 和它对应的控制器中的类以及类中的方法是一一对应的,如: www.test.com/user/info/zhaoyingnan 其中 user 对应的就是控制器中的 us ...
- linux安装hadoop 1.2.1
我的服务器里面会装很多东西,所以我在跟目录下面建立了个doc文档文件夹 1.创建存放软件的doc文件夹 mkdir doc 2.进去doc文件夹进行下载hadoop-1.2.1资源包或者到我的百度云下 ...
- WCF并发控制与实例模式
WCF实例模式类型与区别 实例化模式 instanceMode percall 单调模式 [ServiceBehavior(InstanceContextMode=InstanceCon ...
- apache加载php配置
#载入php模块和ini路径,以及凡是.php开头的以它来处理 LoadModule php5_module E:/server/php/php5apache2_2.dll PHPIniDir &qu ...
- Windows路由表详解
对于路由器的路由表,大部分网管朋友都很熟悉,但是对于windows的路由表,可能了解的人就相对少一些.今天我们就一起来看看windows路由表. 一. windows路由表条目解释 1. 使用ip ...
- UVA 11235 Frequent Values ---RMQ
大白书上的例题,具体讲解见大白书,最好用用一个Log数组直接求k,这样就是纯O(1)了 #include <iostream> #include <cstdio> #inclu ...
- HDU 2491 Priest John's Busiest Day
贪心.. #include<iostream> #include<string.h> #include<math.h> #include <stdio.h&g ...
- 三维网格形变算法(Linear rotation-invariant coordinates和As-Rigid-As-Possible)
在三维网格形变算法中,个人比较喜欢下面两个算法,算法的效果都比较不错, 不同的是文章[Lipman et al. 2005]算法对控制点平移不太敏感.下面分别介绍这两个算法: 文章[Lipman et ...
- Fidder--实现手机的抓包
今天闲着没吊事,来写一篇关于怎么抓取Android中的app数据包?工欲行其事,必先利其器,上网google了一下,发现了一款神器:Fiddler,这个貌似是所有软件开发者必备神器呀!这款工具不仅可以 ...