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了。

时间: 2024-11-09 01:50:33

javascript异步处理的相关文章

JavaScript异步编程设计快速响应的网络应用

JavaScript已然成为了多媒体.多任务.多内核网络世界中的一种单线程语言.其利用事件模型处理异步触发任务的行为成就了JavaScript作为开发语言的利器.如何深入理解和掌握JavaScript异步编程变得尤为重要!!!<JavaScript异步编程设计快速响应的网络应用>提供了一些方法和灵感. 一.深入理解JavaScript事件 1. 事件的调度 JavaScript事件处理器在线程空闲之前不会运行(空闲时运行). var start = new Date(); setTimeout

Javascript异步编程之setTimeout与setInterval详解分析(一)

Javascript异步编程之setTimeout与setInterval 在谈到异步编程时,本人最主要会从以下三个方面来总结异步编程( 注意: 特别解释:是总结,本人也是菜鸟,所以总结不好的,请各位大牛多多原谅!) 1. setTimeout与setInterval详细分析基本原理. 接下来这篇博客会总结setTimeout和setInterval基本点,对于上面三点会分三篇博客分别来总结,对于知道上面三点的人,但是又不是非常了解全面知识点的码农来说,没有关系的,我们可以慢慢来学习,来理解,或

JavaScript异步编程(一) 深入理解JavaScript事件

JavaScript异步编程 深入理解JavaScript事件 ?事件的调度 JavaScript事件处理器在线程空闲之前不会运行 线程的阻塞 var start = new Date(); // setTimeout和setInterval的计时精度比期望值差 setTimeout(function(){ var end = new Date(); console.log('Time elapsed', end - start, 'ms'); }, 500); while(new Date -

javascript异步延时加载及判断是否已加载js/css文件

引用就是某一变量(目标)的一个别名,对引用的操作与对变量直接操作完全一样. 引用的声明方法:类型标识符 &引用名=目标变量名: 例如: int a int &b=a; //定义引用b,它是变量a的引用,即别名 #include <stdio.h> void main() { int a = 123; int &b = a; printf("a=%d b=%d\n", a, b); } 执行结果: 实例:引用和变量的关系 #include <io

JavaScript异步编程(2)- 先驱者:jsDeferred

原文出处: linkFly   欢迎分享原创到伯乐头条 JavaScript当前有众多实现异步编程的方式,最为耀眼的就是ECMAScript 6规范中的Promise对象,它来自于CommonJS小组的努力:Promise/A+规范. 研究javascript的异步编程,jsDeferred也是有必要探索的:因为Promise/A+规范的制定基本上是奠定在jsDeferred上,它是javascript异步编程中里程碑式的作品.jsDeferred自身的实现也是非常有意思的. 本文将探讨项目js

Javascript异步编程方法之------“事件监听”

Javascript异步编程方法之------“事件监听”另一种思路是采用事件驱动模式.任务的执行不取决于代码的顺序,而取决于某个事件是否发生.还是以f1和f2为例.首先,为f1绑定一个事件(这里采用的jQuery的写法).f1.on('done', f2);上面这行代码的意思是,当f1发生done事件,就执行f2.然后,对f1进行改写:function f1(){setTimeout(function () {// f1的任务代码f1.trigger('done');}, 1000);}f1.

JavaScript异步机制

单线程异步执行的JavaScript JavaScript是单线程异步执行的,单线程意味着代码在任务队列中会按照顺序一个接一个的执行.异步代表JavaScript代码在任务队列中的顺序并不完全等同于代码的书写顺序,比如事件绑定.Ajax.setTimeout()等任务的发生时间是“不可被预期”的. 既然JavaScript是单线程机制,那Ajax为什么是异步的?setTimeout()是怎样执行的? 在浏览器中,JavaScript引擎是单线程执行的.也就是说,在同一时间内,只能有一段代码被Ja

Javascript 异步加载详解(转)

本文总结一下浏览器在 javascript 的加载方式. 关键词:异步加载(async loading),延迟加载(lazy loading),延迟执行(lazy execution),async 属性, defer 属性 一.同步加载与异步加载的形式 1. 同步加载 我们平时最常使用的就是这种同步加载形式: <script src="http://yourdomain.com/script.js"></script> 同步模式,又称阻塞模式,会阻止浏览器的后续

JQuery日记6.5 Javascript异步模型(二)

mnesia在频繁操作数据的过程可能会报错:** WARNING ** Mnesia is overloaded: {dump_log, write_threshold},可以看出,mnesia应该是过载了.这个警告在mnesia dump操作会发生这个问题,表类型为disc_only_copies .disc_copies都可能会发生. 如何重现这个问题,例子的场景是多个进程同时在不断地mnesia:dirty_write/2 mnesia过载分析 1.抛出警告是在mnesia 增加dump

获取JavaScript异步函数的返回值

今天研究一个小问题: 怎么拿到JavaScript异步函数的返回值? 1.错误尝试 当年未入行时,我的最初尝试: ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <script> function getSomething() {  var r = 0;  setTimeout(function() {  r = 2;  }, 10);  return r; } function compute() {  var x = getSomething();  alert