当前位置: 首页 > 科技观察

Javascript异步编程详解

时间:2023-03-14 18:42:24 科技观察

前言大家可能知道,Javascript语言的执行环境是“单线程”。所谓“单线程”,就是一次只能完成一个任务。如果有多个任务,则必须排队,完成上一个任务,然后执行下一个任务,依此类推。这种模式的优点是实现起来比较简单,执行环境也比较简单;缺点是只要一个任务耗时长,后面的任务就必须排队等候,会延迟整个程序的执行。常见的浏览器无响应(假死),往往是因为某段Javascript代码运行时间过长(比如死循环),导致整个页面卡在这个地方,无法执行其他任务。为了解决这个问题,Javascript语言将任务执行方式分为同步(Synchronous)和异步(Asynchronous)两种。“异步模式”非常重要。在浏览器端,应该将耗时较长的操作异步执行,以防止浏览器无响应。最好的例子是Ajax操作。在服务器端,“异步模式”甚至是唯一的模式,因为执行环境是单线程的,如果让所有的http请求都同步执行,服务器性能会急剧下降,很快就会无响应。setTimeout函数的缺点延迟处理当然是setTimeout的一个神器。很多人对setTimeout函数的理解是:如果delay为n,则函数会在n毫秒后执行。事实上,情况并非如此。这里存在三个问题:一个是setTimeout函数的时效性,setTimeout有一定的时间间隔,没有设置成n毫秒执行,是n毫秒执行,可能会有一点时间延迟,周期最短setInterval和setTimeout函数的时间约为5毫秒。HTML规范中也提到了这个值:让超时成为第二个方法参数,或者如果参数被省略则为零。如果不写timeout参数,默认为0如果嵌套层数大于5,timeout小于4,则增加timeout为如果嵌套层数大于5,timeout设置的值小于4,那么直接取4。其次,while循环会阻塞setTimeout的执行。看这段代码:vart=true;window.setTimeout(function(){t=false;},1000);while(t){}alert('end');结果是死循环导致setTimeout不执行,也导致alert不执行js是单线程的,所以会先执行while(t){}再alert,但是这个循环体是一个死循环,所以永远不会执行警报。至于为什么不执行setTimeout,是因为js的工作机制是:只有在线程中没有执行同步代码时才会执行异步代码。setTimeout是异步代码,所以setTimeout只能在js空闲的时候执行,但是die循环永远不会空闲,所以setTimeout是永远不会执行的。三是try..catch无法捕捉到他的错误。异步编程方法回调函数这是异步编程最基本的方法。假设有两个函数f1和f2,后者等待前者的执行结果。functionf1(callback){  setTimeout(function(){    //f1任务代码    callback();  },1000);}f1(f2);这样一来,我们就把同步操作变成了异步操作,f1不会阻塞程序的运行,相当于先执行了程序的主要逻辑,延迟了耗时操作的执行。回调函数的优点是简单,易于理解和部署。缺点是不利于代码的阅读和维护。各个部分之间的耦合是高度耦合的(Coupling),流程会很混乱,每个任务只能指定一个回调函数。考虑事件监控的另一种方法是使用事件驱动模型。任务的执行不依赖于代码的顺序,而是依赖于事件是否发生f1.on('done',f2);functionf1(){  setTimeout(function(){    //f1的任务代码    f1.trigger('done');  },1000);}JS和浏览器提供的native方法基本都是基于事件触发机制,耦合度很低,但事件不能被流程控制Promises对象Promises对象是CommonJS工作组提出的规范,为异步编程提供统一的接口。Promises可以简单理解为一个事务,它有三种状态:resolved,因故中断,rejected,还在等待上一个事务结束,pending,简单的说,思想就是每个异步任务返回一个Promises对象,该对象有一个then方法,它允许您指定一个回调函数。这样写的好处是回调函数变成了链式写法,程序流程一目了然。Promises是一个事务管理器。它的作用是将各种内嵌的回调事务以管道的形式表达出来,目的是简化编程,让代码逻辑更加清晰。Promises又可以分为:Promiseswithouterrordelivery,即交易不会因任何原因中断,交易队列中的项目会按顺序处理。在这个过程中,Promises只有pending和resolved两种状态,并没有rejected状态。对于包含错误的Promises,每个事务都必须使用容错机制来获取结果。一旦发生错误,错误信息将传递给下一个事务。如果错误信息会影响到下一笔交易,下一笔交易也会被拒绝。如果没有,可以正常执行下一笔交易,以此类推。这里留个坑,说说generator对异步编程的实现,以及jquery对Deferred对象的封装。简单的说Deferred对象就是jquery的回调函数解决方案。在英文中,defer是“延迟”的意思,所以Deferred对象的意思就是“延迟”到未来某个时间点再执行。首先回顾下jquery的ajax操作的传统写法:    },    error:function(){      alert("发生错误!");    }  });有了Deferred对象后,写法如下:$.ajax("test.html") .done(function(){alert("哈哈,成功!");}) .fail(function(){alert("错误!");});可以看到,done()相当于success方法,fail()相当于error方法。采用链式写入方式后,代码的可读性大大提高。要了解jQuery.Deferred对象,您可以查看下表。when.jsAngularJS内置了KrisKowal的Q框架,和cujoJS的when.js都是Promises/A规范的实现when.jsinstancevargetData=function(){vardeferred=when.defer();$.getJSON(api,function(data){deferred.resolve(data[0]);});returndeferred.promise;}vargetImg=function(src){vardeferred=when.defer();varimg=newImage();img.onload=函数(){deferred.resolve(img);};img.src=src;returndeferred.promise;}varshowImg=function(img){$(img).appendTo($('#container'));}getData().then(getImg).then(showImg);看三行代码,是不是一目了然,很有语义vardeferred=when.defer();定义一个延迟对象。延迟解决(数据);当异步获取数据完成后,将数据作为参数调用deferred对象的resolve方法。返回deferred.promise;返回延迟对象的Promises属性。这里留个坑说说我之前用过的step.js扩展。Javascript既是单线程又是异步的。两者有没有冲突,有什么区别?Answer1:Javascript本身是单线程的,不具备异步特性。由于Javascript的应用场景是浏览器,浏览器本身就是一个典型的GUI工作线程,而GUI工作线程在大多数系统中都是作为事件处理来实现的,以避免阻塞交互,所以产生了Javascript异步基因。此后的一切都源于此。Answer2:JS的单线程是指一个浏览器进程中只有一个JS执行线程,同时只有一段代码在执行(可以用??IE的分页浏览试试效果,打开多个此时页面使用同一个JS执行线程,如果其中一个页面正在执行一个计算量较大的函数,其他窗口的JS将停止工作)。异步机制由浏览器的两个或多个常驻线程完成。比如一个异步请求是由两个常驻线程完成的:JS执行线程和事件触发线程。JS执行线程发起异步请求(此时浏览器会开启一个新的HTTP请求线程来执行请求,此时JS任务已经完成,继续执行线程队列中的其他任务),以及然后在以后的某个时间点,事件触发线程监听到之前发起的HTTP请求完成后,会将完成事件插入到JS执行队列的尾部,等待JS处理。又比如定时触发器(setTimeout和setinterval)是浏览器的定时器线程执行的定时计数,然后在定时时间将定时处理函数的执行请求插入到JS执行队列的尾部(所以在使用这些时两个函数,实际执行时间大于等于指定时间,不保证计时准确)。所以,所谓的单线程异步JS,应该更多的是属于浏览器的行为。它们之间没有冲突,它们不是一回事。没有区别。setTimeout(fn,0)立即执行的问题首先,它不会立即执行,原因:setTimeout(fn,0)的功能很简单,就是把fn放在的***中执行的运行队列。也就是说,不管setTimeout(fn,0)写到哪里,都可以保证在队首执行。js解析器会将setTimeout(fn,0)中的fn推到队列底部,因为是异步操作。有延迟,是16ms还是4ms取决于浏览器是立即执行还是有可能,调用setTimeout时只要满足以下两个条件即可:执行刚到本轮事件的底部环形。恰好此时事件队列为空。那么setTimeout的回调函数就可以立即执行了。当然“立即执行”意味着在任何其他代码之前执行。