走在前端的大道上html
本篇将本身读过的相关 js异步 的文章中,对本身有启发的章节片断总结在这(会对原文进行删改),会不断丰富提炼总结更新。前端
JS 是单线程的语言。
单线程就意味着,全部任务须要排队,前一个任务结束,才会执行后一个任务。若是前一个任务耗时很长,后一个任务就不得不一直等着。jquery
优缺点
这种模式的好处是实现起来比较简单,执行环境相对单纯;坏处是只要有一个任务耗时很长,后面的任务都必须排队等着,会拖延整个程序的执行。常见的浏览器无响应(假死),每每就是由于某一段Javascript代码长时间运行(好比死循环),致使整个页面卡在这个地方,其余任务没法执行。es6
为何单线程
JavaScript的单线程,与它的用途有关。做为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操做DOM。这决定了它只能是单线程,不然会带来很复杂的同步问题。好比,假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另外一个线程删除了这个节点,这时浏览器应该以哪一个线程为准?面试
因此,为了不复杂性,从一诞生,JavaScript就是单线程,这已经成了这门语言的核心特征,未来也不会改变。ajax
为了利用多核CPU的计算能力,HTML5提出Web Worker标准,容许JavaScript脚本建立多个线程,可是子线程彻底受主线程控制,且不得操做DOM。因此,这个新标准并无改变JavaScript单线程的本质。编程
应对方法
为了解决这个问题,Javascript语言将任务的执行模式分红两种:同步(Synchronous)和异步(Asynchronous)。json
因而,全部任务能够分红两种,一种是同步任务(synchronous),另外一种是异步任务(asynchronous)。同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;异步任务指的是,不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务能够执行了,该任务才会进入主线程执行。数组
具体来讲,异步执行的运行机制以下。(同步执行也是如此,由于它能够被视为没有异步任务的异步执行。)promise
(1)全部同步任务都在主线程上执行,造成一个执行栈(execution context stack)。 (2)主线程以外,还存在一个"任务队列"(task queue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。 (3)一旦"执行栈"中的全部同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,因而结束等待状态,进入执行栈,开始执行。 (4)主线程不断重复上面的第三步。
JS的单线程是指一个浏览器进程中只有一个JS的执行线程,同一时刻内只会有一段代码在执行(你可使用IE的标签式浏览试试看效果,这时打开的多个页面使用的都是同一个JS执行线程,若是其中一个页面在执行一个运算量较大的function时,其余窗口的JS就会中止工做)。
而异步机制是浏览器的两个或以上常驻线程共同完成的,例如异步请求是由两个常驻线程:JS执行线程 和 事件触发线程 共同完成的,JS的 执行线程 发起异步请求(这时浏览器会开一条新的 HTTP请求线程 来执行请求,这时JS的任务已完成,继续执行线程队列中剩下的其余任务),而后在将来的某一时刻 事件触发线程 监视到以前的发起的HTTP请求已完成,它就会把完成事件插入到JS执行队列的尾部等待JS处理。又例如定时触发(settimeout和setinterval)是由浏览器的 定时器线程 执行的定时计数,而后在定时时间把定时处理函数的执行请求插入到JS执行队列的尾端(因此用这两个函数的时候,实际的执行时间是大于或等于指定时间的,不保证能准肯定时的)。因此,所谓的JS的单线程和异步更多的应该是属于浏览器的行为
,他们之间没有冲突,更不是同一种事物,没有什么区别不区别的。
小结:
Javascript 自己是单线程的,并无异步的特性。 因为 Javascript 的运用场景是浏览器,浏览器自己是典型的 GUI 工做线程,GUI 工做线程在绝大多数系统中都实现为事件处理,避免阻塞交互,所以产生了 Javascript 异步基因。此后种种都源于此。
JS 中异步操做还挺多的,常见的分如下几种:
JavaScript最基础的异步函数是setTimeout和setInterval。setTimeout会在必定时间后执行给定的函数。它接受一个回调函数做为第一参数和一个毫秒时间做为第二参数。
setTimeout( function() { console.log("Hello!"); }, 1000);
setTimout(setInterval)并非当即就执行的,这段代码意思是,等 1s后,把这个 function 加入任务队列中,若是任务队列中没有其余任务了,就执行输出 'Hello'。
var outerScopeVar; helloCatAsync(); console.log(outerScopeVar); function helloCatAsync() { setTimeout(function() { outerScopeVar = 'hello'; }, 2000); }
执行上面代码,发现 outerScopeVar 输出是 undefined,而不是 hello。之因此这样是由于在异步代码中返回的一个值是不可能给同步流程中使用的,由于 console.log(outerScopeVar) 是同步代码,执行完后才会执行 setTimout。
helloCatAsync(function(result) { console.log(result); }); function helloCatAsync(callback) { setTimeout( function() { callback('hello') } , 1000) }
把上面代码改为,传递一个callback,console 输出就会是 hello。
有趣的是,直到在同一程序段中全部其他的代码执行结束后,超时才会发生。因此若是设置了超时,同时执行了需长时间运行的函数,那么在该函数执行完成以前,超时甚至都不会启动。实际上,异步函数,如setTimeout和setInterval,被压入了称之为Event Loop的队列。
Event Loop是一个回调函数队列。当异步函数执行时,回调函数会被压入这个队列。JavaScript引擎直到异步函数执行完成后,才会开始处理事件循环。这意味着JavaScript代码不是多线程的,即便表现的行为类似。事件循环是一个先进先出(FIFO)队列,这说明回调是按照它们被加入队列的顺序执行的。
var xhr = new XMLHttpRequest(); xhr.onreadystatechange = function() { if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304 ) { console.log(xhr.responseText); } else { console.log( xhr.status); } } xhr.open('GET', 'url', false); xhr.send();
上面这段代码,xhr.open 中第三个参数默认为 false 异步执行,改成 true 时为同步执行。
语法:
new Promise( function(resolve, reject) {...});
console.dir(Promise) ƒ Promise() all:ƒ all() arguments:(...) caller:(...) length:1 name:"Promise" prototype:Promise catch:ƒ catch() constructor:ƒ Promise() finally:ƒ finally() then:ƒ then() Symbol(Symbol.toStringTag):"Promise"__proto__:Object race:ƒ race() reject:ƒ reject() resolve:ƒ resolve() Symbol(Symbol.species):(...) get Symbol(Symbol.species):ƒ [Symbol.species]() __proto__:ƒ () [[Scopes]]:Scopes[0]
这么一看就明白了,Promise是一个构造函数,本身身上有all、reject、resolve这几个眼熟的方法,原型上有then、catch等一样很眼熟的方法。这么说用Promise new出来的对象确定就有then、catch方法。
那就new一个玩玩吧。
var p = new Promise(function(resolve, reject){ //作一些异步操做 setTimeout(function(){ console.log('执行完成'); resolve('随便什么数据'); }, 2000); });
Promise的构造函数接收一个参数,是函数,而且传入两个参数:resolve,reject,分别表示异步操做执行成功后的回调函数 和 异步操做执行失败后的回调函数。其实这里用“成功”和“失败”来描述并不许确,按照标准来说,resolve是将Promise的状态置为fullfiled,reject是将Promise的状态置为rejected,在遇到 resolve 或 reject以前,状态一直是pending。
不过在咱们开始阶段能够先这么理解,后面再细究概念。
在上面的代码中,咱们执行了一个异步操做,也就是setTimeout,2秒后,输出“执行完成”,而且调用resolve方法。 运行代码,会在2秒后输出“执行完成”。注意!我只是new了一个对象,并无调用它,咱们传进去的函数就已经执行了,这是须要注意的一个细节。因此咱们用Promise的时候通常是包在一个函数中,在须要的时候去运行这个函数,如:
function runAsync(){ var p = new Promise(function(resolve, reject){ //作一些异步操做 setTimeout(function(){ console.log('执行完成'); resolve('随便什么数据'); }, 2000); }); return p; } runAsync()
这时候你应该有两个疑问:1.包装这么一个函数有毛线用?2.resolve('随便什么数据');这是干毛的? 咱们继续来说。在咱们包装好的函数最后,会return出Promise对象,也就是说,执行这个函数咱们获得了一个Promise对象。还记得Promise对象上有then、catch方法吧?这就是强大之处了,看下面的代码:
runAsync().then(function(data){ console.log(data); //后面能够用传过来的数据作些其余操做 //...... });
在 runAsync() 的返回上直接调用then方法,then接收一个参数,是函数,而且会拿到咱们在runAsync 中调用resolve时传的的参数。运行这段代码,会在2秒后输出“执行完成”,紧接着输出“随便什么数据”。 这时候你应该有所领悟了,原来then里面的函数就跟咱们平时的回调函数一个意思,可以在runAsync这个异步任务执行完成以后被执行。这就是Promise的做用了,简单来说,就是能把原来的回调写法分离出来,在异步操做执行完后,用链式调用的方式执行回调函数。 你可能会不屑一顾,那么牛逼轰轰的Promise就这点能耐?我把回调函数封装一下,给runAsync传进去不也同样吗,就像这样:
function runAsync(callback){ setTimeout(function(){ console.log('执行完成'); callback('随便什么数据'); }, 2000); } runAsync(function(data){ console.log(data); });
效果也是同样的,还费劲用Promise干吗。那么问题来了,有多层回调该怎么办?若是callback也是一个异步操做,并且执行完后也须要有相应的回调函数,该怎么办呢?总不能再定义一个callback2,而后给callback传进去吧。而Promise的优点在于,能够在then方法中继续写Promise对象并返回,而后继续调用then来进行回调操做。
三个函数runAsync一、runAsync二、runAsync3
function runAsync1(){ var p = new Promise(function(resolve, reject){ //作一些异步操做 setTimeout(function(){ console.log('异步任务1执行完成'); resolve('随便什么数据1'); }, 1000); }); return p; } function runAsync2(){ var p = new Promise(function(resolve, reject){ //作一些异步操做 setTimeout(function(){ console.log('异步任务2执行完成'); resolve('随便什么数据2'); }, 2000); }); return p; } function runAsync3(){ var p = new Promise(function(resolve, reject){ //作一些异步操做 setTimeout(function(){ console.log('异步任务3执行完成'); resolve('随便什么数据3'); }, 2000); }); return p; }
使用Promise的正确场景是这样的:
runAsync1() .then(function(data){ console.log(data); return runAsync2(); }) .then(function(data){ console.log(data); return runAsync3(); }) .then(function(data){ console.log(data); });
这样可以按顺序,每隔两秒输出每一个异步回调中的内容,在runAsync2中传给resolve的数据,能在接下来的then方法中拿到。运行结果以下:
从表面上看,Promise只是可以简化层层回调的写法,而实质上,Promise的精髓是“状态”,用维护状态、传递状态的方式来使得回调函数可以及时调用,它比传递callback函数要简单、灵活的多。
在then方法中,你也能够直接return数据而不是Promise对象,在后面的then中就能够接收到数据了,好比咱们把上面的代码修改为这样:
runAsync1() .then(function(data){ console.log(data); return runAsync2(); }) .then(function(data){ console.log(data); return '直接返回数据'; //这里直接返回数据 }) .then(function(data){ console.log(data); });
那么输出就变成了这样:
到这里,你应该对“Promise是什么玩意”有了最基本的了解。那么咱们接着来看看ES6的Promise还有哪些功能。咱们光用了resolve,还没用reject呢,它是作什么的呢?事实上,咱们前面的例子都是只有“执行成功”的回调,尚未“失败”的状况,reject的做用就是把Promise的状态置为rejected,这样咱们在then中就能捕捉到,而后执行“失败”状况的回调。看下面的代码。
function getNumber(){ var p = new Promise(function(resolve, reject){ //作一些异步操做 setTimeout(function(){ var num = Math.ceil(Math.random()*10); //生成1-10的随机数 if(num<=5){ resolve(num); } else{ reject('数字太大了'); } }, 2000); }); return p; } getNumber() .then( function(data){ console.log('resolved'); console.log(data); }, function(reason, data){ console.log('rejected'); console.log(reason); } );
getNumber函数用来异步获取一个数字,2秒后执行完成,若是数字小于等于5,咱们认为是“成功”了,调用resolve修改Promise的状态。不然咱们认为是“失败”了,调用reject并传递一个参数,做为失败的缘由。 运行getNumber而且在then中传了两个参数,then方法 能够接受两个参数,第一个对应resolve的回调,第二个对应reject的回调。因此咱们可以分别拿到他们传过来的数据。屡次运行这段代码,你会随机获得下面两种结果:
rejected 1
或
rejected 数字太大了
咱们知道Promise对象除了then方法,还有一个catch方法,它是作什么用的呢?其实它 和then的第二个参数同样,用来指定reject的回调,用法是这样:
getNumber() .then(function(data){ console.log('resolved'); console.log(data); }) .catch(function(reason){ console.log('rejected'); console.log(reason); });
效果和写在then的第二个参数里面同样。不过它还有另一个做用:在执行resolve的回调(也就是上面then中的第一个参数)时,若是抛出异常了(代码出错了),那么并不会报错卡死js,而是会进到这个catch方法中。请看下面的代码:
getNumber()
.then(function(data){
console.log('resolved'); console.log(data); console.log(somedata); //此处的somedata未定义
})
.catch(function(reason){
console.log('rejected'); console.log(reason);
});
在resolve的回调中,咱们console.log(somedata);而somedata这个变量是没有被定义的。若是咱们不用Promise,代码运行到这里就直接 在控制台报错了,不往下运行了。可是在这里,会获得这样的结果:
也就是说进到catch方法里面去了,并且把错误缘由传到了reason参数中。即使是有错误的代码也不会报错了,这与咱们的try/catch语句有相同的功能。
Promise的all方法提供了并行执行异步操做的能力,而且在全部异步操做执行完后才执行回调。咱们仍旧使用上面定义好的runAsync一、runAsync二、runAsync3这三个函数,看下面的例子:
Promise .all([runAsync1(), runAsync2(), runAsync3()]) .then(function(results){ console.log(results); });
用Promise.all来执行,all接收一个数组参数,里面的值最终都算返回Promise对象。这样,三个异步操做的并行执行的,等到它们都执行完后才会进到then里面。那么,三个异步操做返回的数据哪里去了呢?都在then里面呢,all会把全部异步操做的结果放进一个数组中传给then,就是上面的results。因此上面代码的输出结果就是:
有了all,你就能够并行执行多个异步操做,而且在一个回调中处理全部的返回数据,是否是很酷?有一个场景是很适合用这个的,一些游戏类的素材比较多的应用,打开网页时,预先加载须要用到的各类资源如图片、flash以及各类静态文件。全部的都加载完后,咱们再进行页面的初始化。
all方法的效果其实是「谁跑的慢,以谁为准执行回调」,那么相对的就有另外一个方法「谁跑的快,以谁为准执行回调」,这就是race方法,这个词原本就是赛跑的意思。race的用法与all同样,咱们把上面runAsync1的延时改成1秒来看一下:
Promise .race([runAsync1(), runAsync2(), runAsync3()]) .then(function(results){ console.log(results); });
这三个异步操做一样是并行执行的。结果你应该能够猜到,1秒后runAsync1已经执行完了,此时then里面的就执行了。结果是这样的:
你猜对了吗?不彻底,是吧。在then里面的回调开始执行时,runAsync2()和runAsync3()并无中止,仍旧再执行。因而再过1秒后,输出了他们结束的标志。 这个race有什么用呢?使用场景仍是不少的,好比咱们能够用race给某个异步请求设置超时时间,而且在超时后执行相应的操做,代码以下:
//请求某个图片资源 function requestImg(){ var p = new Promise(function(resolve, reject){ var img = new Image(); img.onload = function(){ resolve(img); } img.src = 'xxxxxx'; }); return p; } //延时函数,用于给请求计时 function timeout(){ var p = new Promise(function(resolve, reject){ setTimeout(function(){ reject('图片请求超时'); }, 5000); }); return p; } Promise .race([requestImg(), timeout()]) .then(function(results){ console.log(results); }) .catch(function(reason){ console.log(reason); });
requestImg函数会异步请求一张图片,我把地址写为"xxxxxx",因此确定是没法成功请求到的。timeout函数是一个延时5秒的异步操做。咱们把这两个返回Promise对象的函数放进race,因而他俩就会赛跑,若是5秒以内图片请求成功了,那么遍进入then方法,执行正常的流程。若是5秒钟图片还未成功返回,那么timeout就跑赢了,则进入catch,报出“图片请求超时”的信息。运行结果以下:
function ajax(url) { return new Promise(function(resolve, reject) { var xhr = new XMLHttpRequest(); xhr.onreadystatechange = function() { if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304 ) { resovle(xhr.responseText); } else { reject( xhr.status); } } xhr.open('GET', url, false); xhr.send(); }); } ajax('/test.json') .then(function(data){ console.log(data); }) .cacth(function(err){ console.log(err); });
jquery中的Promise,也就是咱们所知道的Deferred对象
事实上,在此以前网上有不少文章在讲jquery Deferred对象了,可是总喜欢把ajax和Deferred混在一块儿讲,容易把人搞混。when、done、promise、success、error、fail、then、resolve、reject、always这么多方法不能揉在一块儿讲,须要把他们捋一捋,哪些是Deferred对象的方法,哪些是ajax的语法糖,咱们须要心知肚明。
jquery用$.Deferred实现了Promise规范,$.Deferred是个什么玩意呢?仍是老方法,打印出来看看,先有个直观印象:
var def = $.Deferred(); console.log(def);
输出以下:
520134-20160329213911379-199799420.png
$.Deferred()返回一个对象,咱们能够称之为Deferred对象,上面挂着一些熟悉的方法如:done、fail、then等。jquery就是用这个Deferred对象来注册异步操做的回调函数,修改并传递异步操做的状态。
Deferred对象的基本用法以下,为了避免与ajax混淆,咱们依旧举setTimeout的例子:
function runAsync(){ var def = $.Deferred(); //作一些异步操做 setTimeout(function(){ console.log('执行完成'); def.resolve('随便什么数据'); }, 2000); return def; } runAsync().then(function(data){ console.log(data) });
在runAsync函数中,咱们首先定义了一个def对象,而后进行一个延时操做,在2秒后调用def.resolve(),最后把def做为函数的返回。调用runAsync的时候将返回def对象,而后咱们就能够.then来执行回调函数。
是否是感受和ES6的Promise很像呢?咱们来回忆一下第一篇中ES6的例子:
function runAsync(){ var p = new Promise(function(resolve, reject){ //作一些异步操做 setTimeout(function(){ console.log('执行完成'); resolve('随便什么数据'); }, 2000); }); return p; } runAsync()
区别在何处一看便知。因为jquery的def对象自己就有resolve方法,因此咱们在建立def对象的时候并未像ES6这样传入了一个函数参数,是空的。在后面能够直接def.resolve()这样调用。
这样也有一个弊端,由于执行runAsync()能够拿到def对象,而def对象上又有resolve方法,那么岂不是能够在外部就修改def的状态了?好比我把上面的代码修改以下:
var d = runAsync(); d.then(function(data){ console.log(data) }); d.resolve('在外部结束');
现象会如何呢?并不会在2秒后输出“执行完成”,而是直接输出“在外部结束”。由于咱们在异步操做执行完成以前,没等他本身resolve,就在外部给resolve了。这显然是有风险的,好比你定义的一个异步操做并指定好回调函数,有可能被别人给提早结束掉,你的回调函数也就不能执行了。
怎么办?jquery提供了一个promise方法,就在def对象上,他能够返回一个受限的Deferred对象,所谓受限就是没有resolve、reject等方法,没法从外部来改变他的状态,用法以下:
function runAsync(){ var def = $.Deferred(); //作一些异步操做 setTimeout(function(){ console.log('执行完成'); def.resolve('随便什么数据'); }, 2000); return def.promise(); //就在这里调用 }
这样返回的对象上就没有resolve方法了,也就没法从外部改变他的状态了。这个promise名字起的有点奇葩,容易让咱们搞混,其实他就是一个返回受限Deferred对象的方法,与Promise规范没有任何关系,仅仅是名字叫作promise罢了。虽然名字奇葩,可是推荐使用。
既然Deferred也是Promise规范的实现者,那么其余特性也必须是支持的。链式调用的用法以下:
var d = runAsync(); d.then(function(data){ console.log(data); return runAsync2(); }) .then(function(data){ console.log(data); return runAsync3(); }) .then(function(data){ console.log(data); });
咱们知道,Promise规范中,then方法接受两个参数,分别是执行完成和执行失败的回调,而jquery中进行了加强,还能够接受第三个参数,就是在pending状态时的回调,以下:
deferred.then( doneFilter [, failFilter ] [, progressFilter ] )
除此以外,jquery还增长了两个语法糖方法,done和fail,分别用来指定执行完成和执行失败的回调,也就是说这段代码:
d.then(function(){ console.log('执行完成'); }, function(){ console.log('执行失败'); });
与这段代码是等价的:
d.done(function(){ console.log('执行完成'); }) .fail(function(){ console.log('执行失败'); });
jquery的Deferred对象上还有一个always方法,不论执行完成仍是执行失败,always都会执行,有点相似ajax中的complete。不赘述了。
jquery中,还有一个$.when方法来实现Promise,与ES6中的all方法功能同样,并行执行异步操做,在全部的异步操做执行完后才执行回调函数。不过$.when并无定义在$.Deferred中,看名字就知道,$.when,它是一个单独的方法。与ES6的all的参数稍有区别,它接受的并非数组,而是多个Deferred对象,以下:
$.when(runAsync(), runAsync2(), runAsync3()) .then(function(data1, data2, data3){ console.log('所有执行完成'); console.log(data1, data2, data3); });
jquery中没有像ES6中的race方法吗?就是以跑的快的为准的那个方法。对的,jquery中没有。
以上就是jquery中Deferred对象的经常使用方法了,还有一些其余的方法用的也很少,干脆就不记它了。接下来该说说ajax了。
jquery的ajax返回一个受限的Deferred对象,还记得受限的Deferred对象吧,也就是没有resolve方法和reject方法,不能从外部改变状态。想一想也是,你发一个ajax请求,别人从其余地方给你取消掉了,也是受不了的。
既然是Deferred对象,那么咱们上面讲到的全部特性,ajax也都是能够用的。好比链式调用,连续发送多个请求:
req1 = function(){ return $.ajax(/*...*/); } req2 = function(){ return $.ajax(/*...*/); } req3 = function(){ return $.ajax(/*...*/); } req1().then(req2).then(req3).done(function(){ console.log('请求发送完毕'); });
明白了ajax返回对象的实质,那咱们用起来就驾轻就熟了。
这三个方法或许是咱们用的最多的,使用起来是这样的:
$.ajax(/*...*/) .success(function(){/*...*/}) .error(function(){/*...*/}) .complete(function(){/*...*/})
分别表示ajax请求成功、失败、结束的回调。这三个方法与Deferred又是什么关系呢?其实就是语法糖,success对应done,error对应fail,complete对应always,就这样,只是为了与ajax的参数名字上保持一致而已,更方便你们记忆,看一眼源码:
deferred.promise( jqXHR ).complete = completeDeferred.add; jqXHR.success = jqXHR.done; jqXHR.error = jqXHR.fail;
complete那一行那么写,是为了减小重复代码,其实就是把done和fail又调用一次,与always中的代码同样。deferred.promise( jqXHR )这句也能看出,ajax返回的是受限的Deferred对象。
jquery加了这么些个语法糖,虽然上手门槛更低了,可是却形成了必定程度的混淆。一些人虽然这么写了好久,却一直不知道其中的原理,在面试的时候只能答出一些皮毛,这是很很差的。这也是我写这篇文章的原因。
jquery中Deferred对象涉及到的方法不少,本文尽可能分门别类的来介绍,但愿能帮你们理清思路。总结一下就是:$.Deferred实现了Promise规范,then、done、fail、always是Deferred对象的方法。
$.when是一个全局的方法,用来并行运行多个异步任务,与ES6的all是一个功能。
$.ajax返回一个Deferred对象,success、error、complete是ajax提供的语法糖,功能与Deferred对象的done、fail、always一致。
语法:
async function name([param[, param[, ... param]]]) { statements }
调用 async 函数时会返回一个 promise 对象。当这个 async 函数返回一个值时,Promise 的 resolve 方法将会处理这个值;当 async 函数抛出异常时,Promise 的 reject 方法将处理这个异常值。
async 函数中可能会有await 表达式,这将会使 async 函数暂停执行,等待 Promise 正常解决后继续执行 async 函数并返回解决结果(异步)。
function resolveAfter2Seconds(x) { return new Promise(resolve => { setTimeout(() => { resolve(x); }, 2000); }); } async function add1(x) { var a = resolveAfter2Seconds(20); var b = resolveAfter2Seconds(30); return x + await a + await b; } add1(10).then(v => { console.log(v); // 2s 后打印 60, 两个 await 是同时发生的,也就是并行的。 }); async function add2(x) { var a = await resolveAfter2Seconds(20); var b = await resolveAfter2Seconds(30); return x + a + b; } add2(10).then(v => { console.log(v); // 4s 后打印 60,按顺序完成的。 });
上面代码是 mdn 的一个例子。
说明了 async 返回的是一个 promise对象;
await 后面跟的表达式是一个 promise,执行到 await时,函数暂停执行,直到该 promise 返回结果,而且暂停但不会阻塞主线程。
await 的任何内容都经过 Promise.resolve() 传递。
await 能够是并行(同时发生)和按顺序执行的。
看下面两段代码:
async function series() { await wait(500); await wait(500); return "done!"; } async function parallel() { const wait1 = wait(500); const wait2 = wait(500); await wait1; await wait2; return "done!"; }
第一段代码执行完毕须要 1000毫秒,这段 await 代码是按顺序执行的;第二段代码执行完毕只须要 500 毫秒,这段 await 代码是并行的。
这是异步编程最基本的方法。
假定有两个函数f1和f2,后者等待前者的执行结果。
f1(); f2();
若是f1是一个很耗时的任务,能够考虑改写f1,把f2写成f1的回调函数。
function f1(callback){ setTimeout(function () { // f1的任务代码 callback(); }, 1000); }
执行代码就变成下面这样:
f1(f2);
采用这种方式,咱们把同步操做变成了异步操做,f1不会堵塞程序运行,至关于先执行程序的主要逻辑,将耗时的操做推迟执行。
回调函数的优势是简单、容易理解和部署,缺点是不利于代码的阅读和维护,各个部分之间高度耦合(Coupling),流程会很混乱,并且每一个任务只能指定一个回调函数。
另外一种思路是采用事件驱动模式。任务的执行不取决于代码的顺序,而取决于某个事件是否发生。
仍是以f1和f2为例。首先,为f1绑定一个事件(这里采用的jQuery的写法)。
f1.on('done', f2);
上面这行代码的意思是,当f1发生done事件,就执行f2。而后,对f1进行改写:
function f1(){ setTimeout(function () { // f1的任务代码 f1.trigger('done'); }, 1000); }
f1.trigger('done')表示,执行完成后,当即触发done事件,从而开始执行f2。
这种方法的优势是比较容易理解,能够绑定多个事件,每一个事件能够指定多个回调函数,并且能够"去耦合"(Decoupling),有利于实现模块化。缺点是整个程序都要变成事件驱动型,运行流程会变得很不清晰。
参考文章
1.Promise
2.JavaScript 运行机制详解:再谈Event Loop
3.知乎问答--知乎用户
4.知乎问答--响马
5.知乎问答--passenger
6.大白话讲解Promise(一)
7.搞懂jquery中的Promise
8.JavaScript异步编程