咱们通常喜欢把异步和同步、并行拿出来比较,我之前的理解老是很模糊,老是生硬地记着“同步就是排队执行,异步就是一块儿执行”,如今一看,当初简直就是傻,因此咱们第一步先把这三个概念搞清楚,我不太喜欢看网上有些博客里很含糊地说“xxxx是同步,xxxx是异步”,还有举什么通俗的例子,其实对不懂的人来讲仍是懵逼。javascript
首先咱们要知道这一切的根源都是“Javascript是单线程”,也就是一次只能作一件事,那么为何是单线程呢?由于js渲染在浏览器上,包含了许多与用户的交互,若是是多线程,那么试想一个场景:一个线程在某个DOM上添加内容,而另外一个线程删除这个DOM,那么浏览器要如何反应呢?这就乱套了。html
单线程下全部的任务都是须要排队的,而这些任务分为两种:同步任务和异步任务,同步任务就是在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;异步任务指的是,不进入主线程
、而进入任务队列
(task queue)的任务,只有任务队列
通知主线程
,某个异步任务能够执行了,该任务才会进入主线程
执行。因此说同步执行其实也是一种只有主线程的异步执行。这里有一个视频关于异步操做是如何被执行的,讲得很是好《what the hack is event loop》,我给你们画个图再来理解一下。java
这里补充说明下不一样的异步操做添加到任务队列的时机不一样,如 onclick, setTimeout, ajax 处理的方式都不一样,这些异步操做是由浏览器内核的 webcore 来执行的,webcore 包含上面提到的3种 webAPI,分别是 DOM Binding、timer、network模块。
onclick 由浏览器内核的 DOM Binding 模块来处理,当事件触发的时候,回调函数会当即添加到任务队列中。
setTimeout 会由浏览器内核的 timer 模块来进行延时处理,当时间到达的时候,才会将回调函数添加到任务队列中。
ajax 则会由浏览器内核的 network 模块来处理,在网络请求完成返回以后,才将回调添加到任务队列中。
最后再来讲下并行,并行是关于可以同时发生的事情,是一种多线程的运行机制,而无论同步异步都是单线程的。git
这个很好理解,同步下前一个事件执行完了才能执行后一个事件,那么要是遇到Ajax请求这种耗时很长的,那页面在这段时间就无法操做了,卡在那儿,更有甚者,万一这个请求因为某种缘由一直没有完成,那页面就block了,很不友好。github
咱们能够经过回调函数
、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(上)