第二十九课: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. ...
随机推荐
- RabbitMQ基本概念和使用
RabbitMQ是一个消息代理,核心原理:发送消息,接收消息. RabbitMQ主要用于组件之间的解耦,消息发送者无需知道消息使用者的存在,反之亦然. 单向解耦 ...
- jquery 地址栏链接与a标签链接匹配 特效代码总结(二)
如题所述,当出现这样的功能,点击某个链接后,给跳转后的该链接地址添加样式,通过添加class为current来增加特殊样式. 如图所示:点击HTML+css3跳转后,给其添加如图样式: js代码如下: ...
- 从c到c++
1,stack模板类(头文件为<stack>)需要定义两个参数:元素类型(必要).容器类型(默认为deque), 定义stack对象 stack <string> s 基本操作 ...
- POJ 1201 Intervals
题意:有n个区间[a,b],每个区间有一个值c.找一个集合中的元素使得每个区间至少有c个元素在这个集合中,问最小的集合大小. 思路:设d[i+1]表示0到i有多少个数在这个集合中,显然对于每个区间,d ...
- smarty变量调节器
smarty中变量调解器的作用:在模板中需要对PHP分配过来的变量在输出之前,对变量进行处理 注册变量调解器方式:$smarty->registerPlugin("modifier&q ...
- H5 canvas绘制出现模糊的问题
在之前做移动端小游戏幸运转盘.九宫格转盘,使用到了 canvas ,也是第一次在项目中使用 canvas 来实现. 近期测试人员反应 canvas 绘制的内容太模糊,心想着用 canvas 绘制出来的 ...
- HYSBZ 2145 悄悄话
2145: 悄悄话 Time Limit: 10 Sec Memory Limit: 259 MBSubmit: 271 Solved: 104[Submit][Status][Discuss] ...
- 004医疗项目-逆向工程-pojo类的创建和对应xml的生成
我们使用mybatis的逆向工程来生成pojo类,省去很多不必要的工作. 我把逆向工程需要的项目如下:
- PATH路径出错导致任何命令都找不到解决方法
1.export PATH=/usr/bin:/usr/sbin:/bin:/sbin:/usr/X11R6/bin这样可以保证命令行命令暂时可以使用.命令执行完之后先不要关闭终端或者cd /usr/ ...
- mac终端命令大全介绍
OSX 的文件系统 OSX 采用的Unix文件系统,所有文件都挂在跟目录 / 下面,所以不在要有Windows 下的盘符概念. 你在桌面上看到的硬盘都挂在 /Volumes 下. 比如接上个叫做 US ...