大家知道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异步处理的更多相关文章

  1. NeHe OpenGL教程 第二十九课:Blt函数

    转自[翻译]NeHe OpenGL 教程 前言 声明,此 NeHe OpenGL教程系列文章由51博客yarin翻译(2010-08-19),本博客为转载并稍加整理与修改.对NeHe的OpenGL管线 ...

  2. python第二十九课——文件读写(复制文件)

    自定义函数:实现文件复制操作有形参(2个) 没有返回值相似版(不用) def copyFile(src,dest): #1.打开两个文件:1个关联读操作,1个关联写操作 fr=open(src,'rb ...

  3. python第二十九课——文件读写(读取读取中文字符)

    演示:读取中文字符 结论: 1).如果不设置encoding,默认使用gbk进行编解码 2).如果编码和解码不一致,最终导致报错,但是一旦设置了errors='ingore',那么就不会报错,而采取乱 ...

  4. JAVA学习第二十九课(经常使用对象API)- String类

    多线程告一段落,開始经常使用对象API的涉及,背也要背下来!.! 日后开发,遇见最多的对象是文字,也就是字符串 String类 字符串是一个特殊对象 字符串一旦初始化就不能够被改变 一.特点 publ ...

  5. 【批处理学习笔记】第二十九课:ASCII码

    前面的例子中,我们已经使用过一次ASCII码了,也就是那个笑脸.ASCII码是图形化的符号,可以用来点缀我们的批处理的.    在cmd窗口中我们可以通过任意一个字符的ASCII码来输入该字符,比如C ...

  6. 潭州课堂25班:Ph201805201 django 项目 第二十九课 docker实例,文件下载前后台实现 (课堂笔记)

    docker 实例 :wq!保存退出 放入一个 html 文件 权限不够,加 sudo 查看本地仓库的 image 运行 docker -- name,后跟个运行名, -p 物理机端口映射到容器端口, ...

  7. python第二十九课——文件读写(写数据的操作)

    演示写数据的操作: 结论:往文件中写入数据,如果文件不存在,先创建文件,再写入内容 #1.打开文件 fw=open(r'd.txt','w',encoding='utf-8') #2.写数据操作 fw ...

  8. python第二十九课——文件读写(readline()和readlines()的使用)

    演示readline()和readlines()的使用: #1.打开文件 f3=open(r'a.txt','r',encoding='gbk') #2.读取数据 content3=f3.readli ...

  9. python第二十九课——文件读写(读取数据操作)

    演示读取数据操作:path=r'a.txt' 1.打开文件f1=open(path,'r') 2.读取数据content1=f1.read(3)print(content1) content1=f1. ...

随机推荐

  1. Codeforces Round #254 DZY Loves Colors

    题意:输入n, m ; 有n给位置, 初始时第i个位置的color为i, colorfulness为0.      有m次操作,一种是把成段的区域color更新为x, 对于更新的区域,每个位置(令第i ...

  2. Linux学习之四——磁盘与文件系统管理

    一.一些基本定义 1. superblock:记录此 filesystem 的整体信息,包括inode/block的总量.使用量.剩余量, 以及文件系统的格式与相关信息等:2. inode:记录档案的 ...

  3. Vmware中Ubuntu的各种问题

    Question1: Ubuntu正在运行的时候手残强行关闭了Vmware软件,重启软件发现如下图所示的状态: ''' 完全黑屏怎么解决,提示信息是recovering journal就是说正在恢复日 ...

  4. Bash Shell read file line by line and substring

    #read one file line by line for line in $(cat test1.txt); do echo $line ; done; #while read split li ...

  5. 探索 OpenStack 之(12):cinder-api Service 处理 HTTP Request 的过程分析

    本文是上一篇 探索 OpenStack 之(11):cinder-api Service 启动过程分析 以及 WSGI / Paste deploy / Router 等介绍> 的后续篇. os ...

  6. UVALive 6263 The Dragon and the knights --统计,直线分平面

    题意:给n条直线,将一个平面分成很多个部分,再给m个骑士的坐标,在一个部分内只要有一个骑士即可保护该部分,问给出的m个骑士是不是保护了所有部分. 解法:计算每个骑士与每条直线的位置关系(上面还是下面) ...

  7. 【转】${sessionScope.user}的使用方法

    EL 全名为Expression Language EL 语法很简单,它最大的特点就是使用上很方便.接下来介绍EL主要的语法结构: ${sessionScope.user.sex} 所有EL都是以${ ...

  8. C++容器的复制

    C++容器的复制不同于Java Java是引用复制,复制的仅仅是对象的引用, 在需要复制容器内对象的副本集合的情况,需要使用Clone方法,而且要注意clone方法的浅拷贝 深拷贝 C++的容器复制 ...

  9. C语言 文件操作7--文件错误处理

    //文件错误处理 #define _CRT_SECURE_NO_WARNINGS #include<stdio.h> #include<stdlib.h> #include&l ...

  10. 如何解决机器重启后MySql服务跑不起来了1067,附解决过程

    今个一位同事的mysql跑不起来了,之前还好好的,问我怎么恢复,我没在她出问题的机器上弄,在自己机器上试的,最终在CTO的指导下解决了,现记录于此. 1,首先把她的mysql安装文件夹.数据结构文件. ...