js异步处理

1、什么是异步?

咱们通常喜欢把异步和同步、并行拿出来比较,我之前的理解老是很模糊,老是生硬地记着“同步就是排队执行,异步就是一块儿执行”,如今一看,当初简直就是傻,因此咱们第一步先把这三个概念搞清楚,我不太喜欢看网上有些博客里很含糊地说“xxxx是同步,xxxx是异步”,还有举什么通俗的例子,其实对不懂的人来讲仍是懵逼。javascript

首先咱们要知道这一切的根源都是“Javascript是单线程”,也就是一次只能作一件事,那么为何是单线程呢?由于js渲染在浏览器上,包含了许多与用户的交互,若是是多线程,那么试想一个场景:一个线程在某个DOM上添加内容,而另外一个线程删除这个DOM,那么浏览器要如何反应呢?这就乱套了。html

单线程下全部的任务都是须要排队的,而这些任务分为两种:同步任务和异步任务,同步任务就是在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;异步任务指的是,不进入主线程、而进入任务队列(task queue)的任务,只有任务队列通知主线程,某个异步任务能够执行了,该任务才会进入主线程执行。因此说同步执行其实也是一种只有主线程的异步执行。这里有一个视频关于异步操做是如何被执行的,讲得很是好《what the hack is event loop》,我给你们画个图再来理解一下。java

event-loop.png

这里补充说明下不一样的异步操做添加到任务队列的时机不一样,如 onclick, setTimeout, ajax 处理的方式都不一样,这些异步操做是由浏览器内核的 webcore 来执行的,webcore 包含上面提到的3种 webAPI,分别是 DOM Binding、timer、network模块。
onclick 由浏览器内核的 DOM Binding 模块来处理,当事件触发的时候,回调函数会当即添加到任务队列中。
setTimeout 会由浏览器内核的 timer 模块来进行延时处理,当时间到达的时候,才会将回调函数添加到任务队列中。
ajax 则会由浏览器内核的 network 模块来处理,在网络请求完成返回以后,才将回调添加到任务队列中。
最后再来讲下并行,并行是关于可以同时发生的事情,是一种多线程的运行机制,而无论同步异步都是单线程的。git

2、为何要用异步操做

这个很好理解,同步下前一个事件执行完了才能执行后一个事件,那么要是遇到Ajax请求这种耗时很长的,那页面在这段时间就无法操做了,卡在那儿,更有甚者,万一这个请求因为某种缘由一直没有完成,那页面就block了,很不友好。github

3、如何实现异步

咱们能够经过回调函数Promise生成器Async/Await等来实现异步。
今天咱们先说最基础的回调函数处理方法来实现,列举几个你们熟悉的使用场景,好比:ajax请求、IO操做、定时器。web

ajax(url, function(){ //这就是回调函数 }); setTimeOut(function(){ //回调函数 }, 1000) 

回调自己是比较好用的,可是随着Javascript愈来愈成熟,对于异步编程领域的发展,回调已经不够用了,体如今如下几点:ajax

一、大脑处理程序是顺序的,对于复杂的回调函数会不易理解,咱们须要一种更同步、更顺序的方式来表达异步。
举例说明:编程

//回调函数实现两数相加 function add(getX, getY, cb){ var x, y; getX(function(xVal){ x=xVal; if(y!=undefined){ cb(x+y); } }); getY(function(){ y=yVal; if(x!=undefined){ cb(x+y); } }); } add(fetchX, fetchY, function(sum){ console.log(sum); }) //Promise实现两数相加 function add(xPromise, yPromise){ return Promise.all([xPromise, yPromise]) .then(function(values){ return value[0] + value[1]; }); } //fetchX()、fetchY()返回相应值的Promise add(fetchX(), fetchY()) .then(function(sum){ console.log(sum); }) 

只看结构是否是Promise的写法更顺序话一些。
二、回调通常会把控制权交给第三方,从而带来信任问题,好比:浏览器

  • 调用回调过早
  • 调用回调过晚(或未调用)
  • 调用回调次数过多或过少
  • 未能传递所需的环境和参数
  • 吞掉可能出现的错误和异常

而Promise的特性就有效地解决了这些问题,它是如何解决的呢?网络

调用回调过早

这种顾虑主要是代码是否会引入类Zalgo效应,也就是一个任务有时会同步完地成,而有时会异步地完成,这将致使竟合状态。
Promise被定义为不能受这种顾虑的影响,由于即使是当即完成的Promise(好比 new Promise(function(resolve){ resolve(42); }))也不可能被同步地 监听。也就是说,但你在Promise上调用then(..)的时候,即使这个Promise已经被解析了,你给then(..)提供的回调也将老是被异步地调用。

调用回调过晚

当一个Promise被调用时,这个Promise 上的then注册的回调函数都会在下一个异步时机点上,按顺序地,被当即调用。这些回调中的任意一个都没法影响或延误对其它回调的调用。
举例说明:

p.then( function(){ p.then( function(){ console.log( "C" ); } ); console.log( "A" ); } ); p.then( function(){ console.log( "B" ); } ); // A B C 

为何“C”没有排到“B”的前面?由于由于“C”所处的.then回调函数是在下一个事件循环tick。

回调未调用

这是一个很常见的顾虑。Promise用几种方式解决它。
首先,当Promise被解析后,在代码不出错的状况下它必定会告知你解析结果。若是代码有错误,归类于后面的“吞掉错误或异常”中。
那若是Promise自己无论怎样永远没有被解析呢?那么Promise会用Promise.race来解决。
看代码示例:

// 一个使Promise超时的工具 function timeoutPromise(delay) { return new Promise( function(resolve,reject){ setTimeout( function(){ reject( "Timeout!" ); }, delay ); } ); } // 为`foo()`设置一个超时 Promise.race( [ foo(), // 尝试调用`foo()` timeoutPromise( 3000 ) // 给它3秒钟 ] ) .then( function(){ // `foo(..)`及时地完成了! }, function(err){ // `foo()`不是被拒绝了,就是它没有及时完成 // 那么能够考察`err`来知道是哪一种状况 } ); 
调用次数过少或过多

正常是调用一次,“过少”就是未被调用,参考上文;“过多”的状况也很容易理解。Promise的定义方式使得它只能被决议一次,若是出于某种状况决议了屡次,Promise也只会接受第一次决议,并忽略后续调用。

未能传递所需的参数/环境值

Promise只会有一个解析结果(完成或拒绝)。若是没有用一个值明确地解析它,它的值就是undefined,就像JS中常见的那样。

吞掉错误或异常

Promise中异常会被捕获,而且使这个Promise被拒绝。
举个例子:

var p = new Promise( function(resolve,reject){ foo.bar(); // `foo`没有定义,因此这是一个错误! resolve( 42 ); // 永远不会跑到这里 :( } ); p.then( function fulfilled(){ // 永远不会跑到这里 :( }, function rejected(err){ // `err`将是一个来自`foo.bar()`那一行的`TypeError`异常对象 } ); 

Promise就先说到这里,关于PromiseAPI及其源码还有生成器、Async/Await 在后续文章中整理报道。
【写得很差的地方请大胆吐槽,很是感谢你们带我进步。】

参考资料:
阮一峰event-loop
王福朋深刻理解javascript异步系列一
你不知道的javascript
你不懂JS: 异步与性能 第三章: Promise(上)


原做者:程序媛Wendy
连接:https://www.jianshu.com/p/f4abe8c4fc2f
相关文章
相关标签/搜索