遠(yuǎn)程辦公”">國(guó)務(wù)院聯(lián)防聯(lián)控機(jī)制新聞發(fā)布會(huì),多次肯定“云辦公”、“遠(yuǎn)程辦公”
1234
2022-05-30
JavaScript是一門單線程語(yǔ)言,即一次只能完成一個(gè)任務(wù),若有多個(gè)任務(wù)要執(zhí)行,則必須排隊(duì)按照隊(duì)列來執(zhí)行(前一個(gè)任務(wù)完成,再執(zhí)行下一個(gè)任務(wù))。
這種模式執(zhí)行簡(jiǎn)單,但隨著日后的需求,事務(wù),請(qǐng)求增多,這種單線程模式執(zhí)行效率必定低下。只要有一個(gè)任務(wù)執(zhí)行消耗了很長(zhǎng)時(shí)間,在這個(gè)時(shí)間里后面的任務(wù)無法執(zhí)行。常見的瀏覽器無響應(yīng)(假死),往往就是因?yàn)槟骋欢蜫avascript代碼長(zhǎng)時(shí)間運(yùn)行(比如死循環(huán)),導(dǎo)致整個(gè)頁(yè)面卡在這個(gè)地方,其他任務(wù)無法執(zhí)行。(弊端)
為了解決這個(gè)問題,JavaScript語(yǔ)言將任務(wù)執(zhí)行模式分成同步和異步:
同步模式: 就是上面所說的一種執(zhí)行模式,后一個(gè)任務(wù)等待前一個(gè)任務(wù)結(jié)束,然后再執(zhí)行,程序的執(zhí)行順序與任務(wù)的排列順序是一致的、同步的。
異步模式: 就是每一個(gè)任務(wù)有一個(gè)或多個(gè)回調(diào)函數(shù)(callback),前一個(gè)任務(wù)結(jié)束后,不是執(zhí)行后一個(gè)任務(wù),而是執(zhí)行回調(diào)函數(shù),后一個(gè)任務(wù)則是不等前一個(gè)任務(wù)結(jié)束就執(zhí)行,所以程序的執(zhí)行順序與任務(wù)的排列順序是不一致的、異步的。
異步模式”非常重要。在瀏覽器端,耗時(shí)很長(zhǎng)的操作都應(yīng)該異步執(zhí)行,避免瀏覽器失去響應(yīng),最好的例子就是Ajax操作。在服務(wù)器端,”異步模式”甚至是唯一的模式,因?yàn)閳?zhí)行環(huán)境是單線程的,如果允許同步執(zhí)行所有http請(qǐng)求,服務(wù)器性能會(huì)急劇下降,很快就會(huì)失去響應(yīng)。(異步模式的重要性)
任務(wù)執(zhí)行順序詳解:??一起談一談js中的宏任務(wù)和微任務(wù)!_紙飛機(jī)博客-CSDN博客
下面就為小伙伴們帶來幾種前端異步解決方案:
一、傳統(tǒng)方案
1、回調(diào)函數(shù)(callback)
異步編程的基本方法。
首先需要聲明,回調(diào)函數(shù)只是一種實(shí)現(xiàn),并不是異步模式特有的實(shí)現(xiàn)。回調(diào)函數(shù)同樣可以運(yùn)用到同步(阻塞)的場(chǎng)景下以及其他一些場(chǎng)景。
回調(diào)函數(shù)的定義:
函數(shù)A作為參數(shù)(函數(shù)引用)傳遞到另一個(gè)函數(shù)B中,并且這個(gè)函數(shù)B執(zhí)行函數(shù)A。我們就說函數(shù)A叫做回調(diào)函數(shù)。如果沒有名稱(函數(shù)表達(dá)式),就叫做匿名回調(diào)函數(shù)。
生活舉例: 約會(huì)結(jié)束后你送你女朋友回家,離別時(shí),你肯定會(huì)說:“到家了給我發(fā)條信息,我很擔(dān)心你。” 然后你女朋友回家以后還真給你發(fā)了條信息。其實(shí)這就是一個(gè)回調(diào)的過程。你留了個(gè)參數(shù)函數(shù)(要求女朋友給你發(fā)條信息)給你女朋友,然后你女朋友回家,回家的動(dòng)作是主函數(shù)。她必須先回到家以后,主函數(shù)執(zhí)行完了,再執(zhí)行傳進(jìn)去的函數(shù),然后你就收到一條信息了。
//定義主函數(shù),回調(diào)函數(shù)作為參數(shù) function A(callback) { callback(); console.log('我是主函數(shù)'); } //定義回調(diào)函數(shù) function B(){ setTimeout("console.log('我是回調(diào)函數(shù)')", 3000);//模仿耗時(shí)操作 } //調(diào)用主函數(shù),將函數(shù)B傳進(jìn)去 A(B); //輸出結(jié)果 //我是主函數(shù) //我是回調(diào)函數(shù)
上面的代碼中,我們先定義了主函數(shù)和回調(diào)函數(shù),然后再去調(diào)用主函數(shù),將回調(diào)函數(shù)傳進(jìn)去。
定義主函數(shù)的時(shí)候,我們讓代碼先去執(zhí)行callback()回調(diào)函數(shù),但輸出結(jié)果卻是后輸出回調(diào)函數(shù)的內(nèi)容。這就說明了主函數(shù)不用等待回調(diào)函數(shù)執(zhí)行完,可以接著執(zhí)行自己的代碼。所以一般回調(diào)函數(shù)都用在耗時(shí)操作上面。比如ajax請(qǐng)求,比如處理文件等。
優(yōu)點(diǎn): 簡(jiǎn)單,容易理解和 部署。
缺點(diǎn): 不利于代碼的閱讀,和維護(hù),各部分之間高度耦合,流程會(huì)很混亂,而且每一個(gè)任務(wù)只能指定一個(gè)回調(diào)函數(shù)。
2、事件監(jiān)聽
采用事件驅(qū)動(dòng)模式。
任務(wù)的執(zhí)行不取決代碼的順序,而取決于某一個(gè)事件是否發(fā)生。
監(jiān)聽函數(shù)有:on,bind,listen,addEventListener,observe等。
以f1和f2為例。首先,為f1綁定一個(gè)事件(采用jquery寫法)。
f1.on('done',f2); //上面代碼意思是,當(dāng)f1發(fā)生done事件,就執(zhí)行f2。
然后對(duì)f1進(jìn)行改寫:
function f1(){ settimeout(function(){ //f1的任務(wù)代碼 f1.trigger('done'); },1000); }
f1.trigger(‘done’)表示,執(zhí)行完成后,立即觸發(fā)done事件,從而開始執(zhí)行f2。
優(yōu)點(diǎn): 比較容易理解,可以綁定多個(gè)事件,每一個(gè)事件可以指定多個(gè)回調(diào)函數(shù),而且可以去耦合,有利于實(shí)現(xiàn)模塊化。
缺點(diǎn): 整個(gè)程序都要變成事件驅(qū)動(dòng)型,運(yùn)行流程會(huì)變得不清晰。
典型事件監(jiān)聽案例
(1).onclick方法:
element.onclick = function(){ //處理函數(shù) }
優(yōu)點(diǎn):寫法兼容到主流瀏覽器。
缺點(diǎn):當(dāng)同一個(gè)element元素綁定多個(gè)事件時(shí),只有最后一個(gè)事件會(huì)被添加。
例如:
element.onclick = handler1; element.onclick = handler2; element.onclick = handler3;
上述只有handler3會(huì)被添加執(zhí)行,所以我們使用另外一種方法(attachEvent和addEvenListener)添加事件。
(2).attachEvent和addEvenListener方法:
//IE:attachEvent(IE下的事件監(jiān)聽) elment.attachEvent("onclick",handler1); elment.attachEvent("onclick",handler2); elment.attachEvent("onclick",handler3);
上述三個(gè)方法執(zhí)行順序:3 - 2 - 1。
//標(biāo)準(zhǔn)addEventListener(標(biāo)準(zhǔn)下的監(jiān)聽) elment.addEvenListener("click",handler1,false); elment.addEvenListener("click",handler2,false); elment.addEvenListener("click",handler3,false);
上述執(zhí)行順序:1 - 2 - 3。
PS:該方法的第三個(gè)參數(shù)是冒泡獲取(useCapture),是一個(gè)布爾值:當(dāng)為false時(shí)表示由里向外(事件冒泡),true表示由外向里(事件捕獲)。
document.getElementById("id1").addEventListener("click",function(){ console.log('id1'); },false); document.getElementById("id2").addEventListener("click",function(){ console.log('id2'); },false); //點(diǎn)擊id=id2的div,先在console中輸出,先輸出id2,再輸出id1 document.getElementById("id1").addEventListener("click",function(){ console.log('id1'); },false); document.getElementById("id2").addEventListener("click",function(){ console.log('id2'); },true); //點(diǎn)擊id=id2的div,先在console中輸出,先輸出id1,再輸出id2
(3).DOM方法addEventListener()和removeListenner():
addEventListenner()和removeListenner()表示用來分配和刪除事件的函數(shù)。這兩種方法都需要三種參數(shù),分別為:string(事件名稱),要觸發(fā)事件的函數(shù)function,指定事件的處理函數(shù)的時(shí)期或者階段(boolean)。例子見(2)
(4).通用的時(shí)間添加方法:
on:function(elment,type,handler){ //添加事件 return element.attachEvent?elment.attachEvent("on"+type,handler):elment.addEventListener(type,handler,false); }
二、工具方案
工具方案大致分為以下5個(gè):
Promise
gengerator函數(shù)
async await
node.js中 nextTick setImmidate
第三方庫(kù) async.js
下面針對(duì)每一個(gè)做詳細(xì)說明應(yīng)用:
1、Promise(重點(diǎn))
Promise的含義和發(fā)展
含義: Promise 對(duì)象用于一個(gè)異步操作的最終完成(或失敗)及其結(jié)果值的表示。簡(jiǎn)單點(diǎn)說,它就是用于處理異步操作的,異步處理成功了就執(zhí)行成功的操作,異步處理失敗了就捕獲錯(cuò)誤或者停止后續(xù)操作。
發(fā)展: Promise 是異步編程的一種解決方案,比傳統(tǒng)的解決方案(回調(diào)函數(shù)和事件監(jiān)聽)更合理和更強(qiáng)大。它由社區(qū)最早提出和實(shí)現(xiàn),ES6將其寫進(jìn)了語(yǔ)言標(biāo)準(zhǔn),統(tǒng)一了語(yǔ)法,原生提供了Promise。
它的一般形式:
new Promise( /* executor */ function(resolve, reject) { if (/* success */) { // ...執(zhí)行代碼 resolve(); } else { /* fail */ // ...執(zhí)行代碼 reject(); } } );
其中,Promise中的參數(shù)executor是一個(gè)執(zhí)行器函數(shù),它有兩個(gè)參數(shù)resolve和reject。它內(nèi)部通常有一些異步操作,如果異步操作成功,則可以調(diào)用resolve()來將該實(shí)例的狀態(tài)置為fulfilled,即已完成的,如果一旦失敗,可以調(diào)用reject()來將該實(shí)例的狀態(tài)置為rejected,即失敗的。
我們可以把Promise對(duì)象看成是一條工廠的流水線,對(duì)于流水線來說,從它的工作職能上看,它只有三種狀態(tài),一個(gè)是初始狀態(tài)(剛開機(jī)的時(shí)候),一個(gè)是加工產(chǎn)品成功,一個(gè)是加工產(chǎn)品失敗(出現(xiàn)了某些故障)。同樣對(duì)于Promise對(duì)象來說,它也有三種狀態(tài):pending: 初始狀態(tài),也稱為未定狀態(tài),就是初始化Promise時(shí),調(diào)用executor執(zhí)行器函數(shù)后的狀態(tài)。 fulfilled:完成狀態(tài),意味著異步操作成功。
pending: 初始狀態(tài),也稱為未定狀態(tài),就是初始化Promise時(shí),調(diào)用executor執(zhí)行器函數(shù)后的狀態(tài)。
fulfilled: 完成狀態(tài),意味著異步操作成功。
rejected: 失敗狀態(tài),意味著異步操作失敗。
它只有兩種狀態(tài)可以轉(zhuǎn)化,即:
操作成功: pending -> fulfilled
操作失敗: pending -> rejected
注意:并且這個(gè)狀態(tài)轉(zhuǎn)化是單向的,不可逆轉(zhuǎn),已經(jīng)確定的狀態(tài)(fulfilled/rejected)無法轉(zhuǎn)回初始狀態(tài)(pending)。
Promise對(duì)象的方法(api)
(1).Promise.prototype.then(callback)
Promise對(duì)象含有then方法,then()調(diào)用后返回一個(gè)Promise對(duì)象,意味著實(shí)例化后的Promise對(duì)象可以進(jìn)行鏈?zhǔn)秸{(diào)用,而且這個(gè)then()方法可以接收兩個(gè)函數(shù),一個(gè)是處理成功后的函數(shù),一個(gè)是處理錯(cuò)誤結(jié)果的函數(shù)。
如下:
var promise1 = new Promise(function(resolve, reject) { // 2秒后置為接收狀態(tài) setTimeout(function() { resolve('success'); }, 2000); }); promise1.then(function(data) { console.log(data); // success }, function(err) { console.log(err); // 不執(zhí)行 }).then(function(data) { // 上一步的then()方法沒有返回值 console.log('鏈?zhǔn)秸{(diào)用:' + data); // 鏈?zhǔn)秸{(diào)用:undefined }).then(function(data) { // .... });
在這里我們主要關(guān)注promise1.then()方法調(diào)用后返回的Promise對(duì)象的狀態(tài),是pending還是fulfilled,或者是rejected?
返回的這個(gè)Promise對(duì)象的狀態(tài)主要是根據(jù)promise1.then()方法返回的值,大致分為以下幾種情況:
如果then()方法中返回了一個(gè)參數(shù)值,那么返回的Promise將會(huì)變成接收狀態(tài)。
如果then()方法中拋出了一個(gè)異常,那么返回的Promise將會(huì)變成拒絕狀態(tài)。
如果then()方法調(diào)用resolve()方法,那么返回的Promise將會(huì)變成接收狀態(tài)。
如果then()方法調(diào)用reject()方法,那么返回的Promise將會(huì)變成拒絕狀態(tài)。
如果then()方法返回了一個(gè)未知狀態(tài)(pending)的Promise新實(shí)例,那么返回的新Promise就是未知 狀態(tài)。
如果then()方法沒有明確指定的resolve(data)/reject(data)/return data時(shí),那么返回的新Promise就是接收狀態(tài),可以一層一層地往下傳遞。
(2).Promise.prototype.catch(callback)
catch()方法和then()方法一樣,都會(huì)返回一個(gè)新的Promise對(duì)象,它主要用于捕獲異步操作時(shí)出現(xiàn)的異常。因此,我們通常省略then()方法的第二個(gè)參數(shù),把錯(cuò)誤處理控制權(quán)轉(zhuǎn)交給其后面的catch()函數(shù),如下:
var promise3 = new Promise(function(resolve, reject) { setTimeout(function() { reject('reject'); }, 2000); }); promise3.then(function(data) { console.log('這里是fulfilled狀態(tài)'); // 這里不會(huì)觸發(fā) // ... }).catch(function(err) { // 最后的catch()方法可以捕獲在這一條Promise鏈上的異常 console.log('出錯(cuò):' + err); // 出錯(cuò):reject });
(3).Promise.all()
Promise.all()接收一個(gè)參數(shù),它必須是可以迭代的,比如數(shù)組。
它通常用來處理一些并發(fā)的異步操作,即它們的結(jié)果互不干擾,但是又需要異步執(zhí)行。它最終只有兩種狀態(tài):成功或者失敗。
指的是將數(shù)組中所有的任務(wù)執(zhí)行完成之后, 才執(zhí)行.then 中的任務(wù)
它的狀態(tài)受參數(shù)內(nèi)各個(gè)值的狀態(tài)影響,即里面狀態(tài)全部為fulfilled時(shí),它才會(huì)變成fulfilled,否則變成rejected。
成功調(diào)用后返回一個(gè)數(shù)組,數(shù)組的值是有序的,即按照傳入?yún)?shù)的數(shù)組的值操作后返回的結(jié)果。
如下:
const p1 = new Promise((resolve,reject)=>{ setTimeout(()=>{ resolve(console.log('p1 任務(wù)1')) },1000) }) .then( data => { console.log('p1 任務(wù)2') }) .then( res => { console.log('p1 任務(wù)3') }) .catch( err =>{ throw err} ) const p2 = new Promise((resolve,reject)=>{ resolve(console.log('p2 任務(wù)1')) }).then( data => { console.log('p2 任務(wù)2') } ).catch( err => { throw err } ) //只有在p1,p2都執(zhí)行完后才會(huì)執(zhí)行then里的內(nèi)容 Promise.all([p1,p2]) .then(()=>console.log('done'))
(4).Promise.race()
Promise.race()和Promise.all()類似,都接收一個(gè)可以迭代的參數(shù),但是不同之處是Promise.race()的狀態(tài)變化不是全部受參數(shù)內(nèi)的狀態(tài)影響,一旦參數(shù)內(nèi)有一個(gè)值的狀態(tài)發(fā)生的改變,那么該P(yáng)romise的狀態(tài)就是改變的狀態(tài)。就跟race單詞的字面意思一樣,誰跑的快誰贏。如下:
var p1 = new Promise(function(resolve, reject) { setTimeout(resolve, 300, 'p1 doned'); }); var p2 = new Promise(function(resolve, reject) { setTimeout(resolve, 50, 'p2 doned'); }); var p3 = new Promise(function(resolve, reject) { setTimeout(reject, 100, 'p3 rejected'); }); Promise.race([p1, p2, p3]).then(function(data) { // 顯然p2更快,所以狀態(tài)變成了fulfilled // 如果p3更快,那么狀態(tài)就會(huì)變成rejected console.log(data); // p2 doned }).catch(function(err) { console.log(err); // 不執(zhí)行 });
(5).Promise.resolve()
Promise.resolve()接受一個(gè)參數(shù)值,可以是普通的值,具有then()方法的對(duì)象和Promise實(shí)例。正常情況下,它返回一個(gè)Promise對(duì)象,狀態(tài)為fulfilled。但是,當(dāng)解析時(shí)發(fā)生錯(cuò)誤時(shí),返回的Promise對(duì)象將會(huì)置為rejected態(tài)。如下:
// 參數(shù)為普通值 var p4 = Promise.resolve(5); p4.then(function(data) { console.log(data); // 5 }); // 參數(shù)為含有then()方法的對(duì)象 var obj = { then: function() { console.log('obj 里面的then()方法'); } }; var p5 = Promise.resolve(obj); p5.then(function(data) { // 這里的值時(shí)obj方法里面返回的值 console.log(data); // obj 里面的then()方法 }); // 參數(shù)為Promise實(shí)例 var p6 = Promise.resolve(7); var p7 = Promise.resolve(p6); p7.then(function(data) { // 這里的值時(shí)Promise實(shí)例返回的值 console.log(data); // 7 }); // 參數(shù)為Promise實(shí)例,但參數(shù)是rejected態(tài) var p8 = Promise.reject(8); var p9 = Promise.resolve(p8); p9.then(function(data) { // 這里的值時(shí)Promise實(shí)例返回的值 console.log('fulfilled:'+ data); // 不執(zhí)行 }).catch(function(err) { console.log('rejected:' + err); // rejected: 8 });
(6).Promise.reject()
Promise.reject()和Promise.resolve()正好相反,它接收一個(gè)參數(shù)值reason,即發(fā)生異常的原因。此時(shí)返回的Promise對(duì)象將會(huì)置為rejected態(tài)。如下:
var p10 = Promise.reject('手動(dòng)拒絕'); p10.then(function(data) { console.log(data); // 這里不會(huì)執(zhí)行,因?yàn)槭莚ejected態(tài) }).catch(function(err) { console.log(err); // 手動(dòng)拒絕 }).then(function(data) { // 不受上一級(jí)影響 console.log('狀態(tài):fulfilled'); // 狀態(tài):fulfilled });
總之,除非Promise.then()方法內(nèi)部拋出異常或者是明確置為rejected態(tài),否則它返回的Promise的狀態(tài)都是fulfilled態(tài),即完成態(tài),并且它的狀態(tài)不受它的上一級(jí)的狀態(tài)的影響。
2、gengerator函數(shù)
在異步編程中,還有一種常用的解決方案,它就是Generator生成器函數(shù)。顧名思義,它是一個(gè)生成器,它也是一個(gè)狀態(tài)機(jī),內(nèi)部擁有值及相關(guān)的狀態(tài),生成器返回一個(gè)迭代器Iterator對(duì)象,我們可以通過這個(gè)迭代器,手動(dòng)地遍歷相關(guān)的值、狀態(tài),保證正確的執(zhí)行順序。
es6 提供的 generator函數(shù)。
總得來說就三點(diǎn):
在function關(guān)鍵字后加一個(gè) , 那么這個(gè)函數(shù)就稱之為generator函數(shù)
*函數(shù)體有關(guān)鍵字 yield , 后面跟每一個(gè)任務(wù) , 也可以有return關(guān)鍵字, 保留一個(gè)數(shù)據(jù)
*通過next函數(shù)調(diào)用, 幾個(gè)調(diào)用, 就是幾個(gè)人任務(wù)執(zhí)行
簡(jiǎn)單使用
Generator的聲明方式類似一般的函數(shù)聲明,只是多了個(gè) ***** 號(hào),并且一般可以在函數(shù)內(nèi)看到y(tǒng)ield關(guān)鍵字。
function* showWords() { yield 'one'; yield 'two'; return 'three'; } var show = showWords(); show.next() // {done: false, value: "one"} show.next() // {done: false, value: "two"} show.next() // {done: true, value: "three"} show.next() // {value: underfined, done: true}
如上代碼,定義了一個(gè)showWords的生成器函數(shù),調(diào)用之后返回了一個(gè)迭代器對(duì)象(即show)。
調(diào)用next方法后,函數(shù)內(nèi)執(zhí)行第一條yield語(yǔ)句,輸出當(dāng)前的狀態(tài)done(迭代器是否遍歷完成)以及相應(yīng)值(一般為yield關(guān)鍵字后面的運(yùn)算結(jié)果)。
每調(diào)用一次next,則執(zhí)行一次yield語(yǔ)句,并在該處暫停,return完成之后,就退出了生成器函數(shù),后續(xù)如果還有yield操作就不再執(zhí)行了。
當(dāng)然還有以下情況:(next()數(shù)量小于yield)
function* g1(){ yield '任務(wù)1' yield '任務(wù)2' yield '任務(wù)3' return '任務(wù)4' } const g1done = g1() console.log(g1done.next()) //{ value: '任務(wù)1', done: false } console.log(g1done.next()) //{ value: '任務(wù)2', done: false }
加上yield和yield*
有時(shí)候,我們會(huì)看到y(tǒng)ield之后跟了一個(gè)*號(hào),它是什么,有什么用呢?
類似于生成器前面的*號(hào),yield后面的星號(hào)也跟生成器有關(guān),舉個(gè)大栗子:
function* showWords() { yield 'one'; yield showNumbers(); return 'three'; } function* showNumbers() { yield 10 + 1; yield 12; } var show = showWords(); show.next() // {done: false, value: "one"} show.next() // {done: false, value: showNumbers} show.next() // {done: true, value: "three"} show.next() // {done: true, value: undefined}
增添了一個(gè)生成器函數(shù),我們想在showWords中調(diào)用一次,簡(jiǎn)單的 yield showNumbers()之后發(fā)現(xiàn)并沒有執(zhí)行函數(shù)里面的yield 10+1。
因?yàn)閥ield只能原封不動(dòng)地返回右邊運(yùn)算后值,但現(xiàn)在的showNumbers()不是一般的函數(shù)調(diào)用,返回的是迭代器對(duì)象。
所以換個(gè)yield* 讓它自動(dòng)遍歷進(jìn)該對(duì)象:
function* showWords() { yield 'one'; yield* showNumbers(); return 'three'; } function* showNumbers() { yield 10 + 1; yield 12; } var show = showWords(); show.next() // {done: false, value: "one"} show.next() // {done: false, value: 11} show.next() // {done: false, value: 12} show.next() // {done: true, value: "three"}
要注意的是,這yield和yield* 只能在generator函數(shù)內(nèi)部使用,一般的函數(shù)內(nèi)使用會(huì)報(bào)錯(cuò)。
function showWords() { yield 'one'; // Uncaught SyntaxError: Unexpected string }
雖然換成yield*不會(huì)直接報(bào)錯(cuò),但使用的時(shí)候還是會(huì)有問題,因?yàn)椤痮ne’字符串中沒有Iterator接口,沒有yield提供遍歷:
function showWords() { yield* 'one'; } var show = showWords(); show.next() // Uncaught ReferenceError: yield is not defined
在爬蟲開發(fā)中,我們常常需要請(qǐng)求多個(gè)地址,為了保證順序,引入Promise對(duì)象和Generator生成器函數(shù),看這個(gè)簡(jiǎn)單的栗子:
var urls = ['url1', 'url2', 'url3']; function* request(urls) { urls.forEach(function(url) { yield req(url); }); // for (var i = 0, j = urls.length; i < j; ++i) { // yield req(urls[i]); // } } var r = request(urls); r.next(); function req(url) { var p = new Promise(function(resolve, reject) { $.get(url, function(rs) { resolve(rs); }); }); p.then(function() { r.next(); }).catch(function() { }); }
上述代碼中forEach遍歷url數(shù)組,匿名函數(shù)內(nèi)部不能使用yield關(guān)鍵字,改換成注釋中的for循環(huán)就行了。
next()調(diào)用中的傳參
參數(shù)值有注入的功能,可改變上一個(gè)yield的返回值,如:
function* showNumbers() { var one = yield 1; var two = yield 2 * one; yield 3 * two; } var show = showNumbers(); show.next().value // 1 show.next().value // NaN show.next(2).value // 6
第一次調(diào)用next之后返回值one為1,但在第二次調(diào)用next的時(shí)候one其實(shí)是undefined的,因?yàn)間enerator不會(huì)自動(dòng)保存相應(yīng)變量值,我們需要手動(dòng)的指定,這時(shí)two值為NaN,在第三次調(diào)用next的時(shí)候執(zhí)行到y(tǒng)ield 3 * two,通過傳參將上次yield返回值two設(shè)為2,得到結(jié)果。
另一個(gè)栗子:
由于ajax請(qǐng)求涉及到網(wǎng)絡(luò),不好處理,這里用了setTimeout模擬ajax的請(qǐng)求返回,按順序進(jìn)行,并傳遞每次返回的數(shù)據(jù):
var urls = ['url1', 'url2', 'url3']; function* request(urls) { var data; for (var i = 0, j = urls.length; i < j; ++i) { data = yield req(urls[i], data); } } var r = request(urls); r.next(); function log(url, data, cb) { setTimeout(function() { cb(url); }, 1000); } function req(url, data) { var p = new Promise(function(resolve, reject) { log(url, data, function(rs) { if (!rs) { reject(); } else { resolve(rs); } }); }); p.then(function(data) { console.log(data); r.next(data); }).catch(function() { }); }
達(dá)到了按順序請(qǐng)求三個(gè)地址的效果,初始直接r.next()無參數(shù),后續(xù)通過r.next(data)將data數(shù)據(jù)傳入。
注意代碼的第16行,這里參數(shù)用了url變量,是為了和data數(shù)據(jù)做對(duì)比。
因?yàn)槌跏糿ext()沒有參數(shù),若是直接將url換成data的話,就會(huì)因?yàn)閜romise對(duì)象的數(shù)據(jù)判斷 !rs == undefined 而reject。
所以將第16行換成 cb(data || url)。
通過模擬的ajax輸出,可了解到next的傳參值,第一次在log輸出的是 url = 'url1’值,后續(xù)將data = 'url1’傳入req請(qǐng)求,在log中輸出 data = 'url1’值。
for…of循環(huán)代替.next()
除了使用.next()方法遍歷迭代器對(duì)象外,通過ES6提供的新循環(huán)方式for…of也可遍歷,但與next不同的是,它會(huì)忽略return返回的值,如:
function* showNumbers() { yield 1; yield 2; return 3; } var show = showNumbers(); for (var n of show) { console.log(n) // 1 2 }
此外,處理for…of循環(huán),具有調(diào)用迭代器接口的方法方式也可遍歷生成器函數(shù),如擴(kuò)展運(yùn)算符…的使用:
function* showNumbers() { yield 1; yield 2; return 3; } var show = showNumbers(); [...show] // [1, 2, length: 2]
更多使用可以參考:MDN - Generator。
3、async await (重點(diǎn))
es7新增的 async函數(shù)。
可以更舒適地與promise協(xié)同工作,它叫做async/await,它是非常的容易理解和使用。
格式以及知識(shí)點(diǎn)
async function aa(){ await '任務(wù)1' await '任務(wù)2' }
async:
讓我們先從async關(guān)鍵字說起,它被放置在一個(gè)函數(shù)前面。就像下面這樣:
async function timeout() { return 'hello world'; }
函數(shù)前面的async一詞意味著一個(gè)簡(jiǎn)單的事情:這個(gè)函數(shù)總是返回一個(gè)promise,如果代碼中有return <非promise>語(yǔ)句,JavaScript會(huì)自動(dòng)把返回的這個(gè)value值包裝成promise的resolved值。
例如,上面的代碼返回resolved值為1的promise,我們可以測(cè)試一下
async function f() { return 1 } f().then(alert) // 彈出1
我們也可以顯式的返回一個(gè)promise,這個(gè)將會(huì)是同樣的結(jié)果:
async function f() { return Promise.resolve(1) } f().then(alert) // 彈出1
所以,async確保了函數(shù)返回一個(gè)promise,即使其中包含非promise,這樣都不需要你來書寫繁雜的Promise,夠簡(jiǎn)單了吧?但是不僅僅只是如此,還有另一個(gè)關(guān)鍵詞await,只能在async函數(shù)里使用,同樣,它也很cool。
await:
// 只能在async函數(shù)內(nèi)部使用 let value = await promise
關(guān)鍵詞await可以讓JavaScript進(jìn)行等待,直到一個(gè)promise執(zhí)行并返回它的結(jié)果,JavaScript才會(huì)繼續(xù)往下執(zhí)行。
以下是一個(gè)promise在1s之后resolve的例子:
async function f() { let promise = new Promise((resolve, reject) => { setTimeout(() => resolve('done!'), 1000) }) let result = await promise // 直到promise返回一個(gè)resolve值(*) alert(result) // 'done!' } f()
函數(shù)執(zhí)行到(await)行會(huì)‘暫停’,不再往下執(zhí)行, 當(dāng)promise處理完成后重新恢復(fù)運(yùn)行, resolve的值成了最終的result,所以上面的代碼會(huì)在1s后輸出’done!’
我們強(qiáng)調(diào)一下:await字面上使得JavaScript等待,直到promise處理完成,
然后將結(jié)果繼續(xù)下去。這并不會(huì)花費(fèi)任何的cpu資源,因?yàn)橐婺軌蛲瑫r(shí)做其他工作:執(zhí)行其他腳本,處理事件等等。
這只是一個(gè)更優(yōu)雅的得到promise值的語(yǔ)句,它比promise更加容易閱讀和書寫。
注意不:能在常規(guī)函數(shù)里使用await
如果我們?cè)噲D在非async函數(shù)里使用await,就會(huì)出現(xiàn)一個(gè)語(yǔ)法錯(cuò)誤:
function f() { let promise = Promise.resolve(1) let result = await promise // syntax error } //Uncaught SyntaxError: await is only valid in async function
如果我們忘記了在函數(shù)之前放置async,我們就會(huì)得到這樣一個(gè)錯(cuò)誤。如上所述,await只能在async函數(shù)中工作。
就以前面幾個(gè)案例可能還看不出async/await 的作用,如果我們要計(jì)算3個(gè)數(shù)的值,然后把得到的值進(jìn)行輸出呢?
async function testResult() { let first = await doubleAfter2seconds(30); let second = await doubleAfter2seconds(50); let third = await doubleAfter2seconds(30); console.log(first + second + third); }
6秒后,控制臺(tái)輸出220, 我們可以看到,寫異步代碼就像寫同步代碼一樣了,再也沒有回調(diào)地域了。
再來一個(gè)看看:先來個(gè)問題
readFile(’./01-Promise.js’) 運(yùn)行結(jié)果是Promise, 但是我們使用 async await之后, 它的結(jié)果是具體的數(shù)據(jù)了?
用到了Node.js里的fs模塊,fs模塊是文件模塊,可以操作文件,readFile()是讀一個(gè)文件,不了解的可以看Node.js官方文檔
const fs = require('fs')//導(dǎo)入fs模塊 const readFile = (filename) =>{ return new Promise((resolve,reject)=>{ fs.readFile(filename,(err,data)=>{ resolve(data.toString()) }) }) } const asyncFn = async() => { //const f0 = eadFile('./01-Promise.js') //類似{value: '文件內(nèi)容', done: false} const f1 = await readFile('./01-Promise.js') //文件內(nèi)容 //const f1 = readFile('./01-Promise.js').then(data=>data) const f2 = await readFile('./02-generator.js') //文件內(nèi)容 console.log( f1 ) console.log( f2 ) } asyncFn()
readFile()定義了一個(gè)Promise方法讀取文件,這里有個(gè)坑,我們現(xiàn)在是在里面返回出數(shù)據(jù)了的,要知道這里面有3層函數(shù),如果不用new Promise這個(gè)方法,大家可以試試用常規(guī)方法能不能返回?cái)?shù)據(jù),先透?jìng)€(gè)底拿不到,大家可以試試。
asyncFn()輸出了文件內(nèi)容,在const f1 = eadFile(’./01-Promise.js’)這一句這一句會(huì)打印出出一個(gè)Promise{‘文件內(nèi)容’},有點(diǎn)類似前面的generator函數(shù)輸出的{value: ‘’, done: false},只不過省略了done,大家知道,我們讀文件,肯定是要里面的內(nèi)容的,如果輸出 Promise{‘文件內(nèi)容’} ,我們是不好取出內(nèi)容的,但是await很好的幫我們解決了這個(gè)問題,前面加上await直接輸出了文件內(nèi)容。
所以:這個(gè)問題可以有個(gè)小總結(jié)
async函數(shù)使用了generator函數(shù)的語(yǔ)法糖 , 它直接生成對(duì)象 {value: ‘’,done:false} await 直接將value提取出來了
通過Promise + async,我們可以把多層函數(shù)嵌套(異步執(zhí)行)的里層函數(shù)得到的數(shù)據(jù) 返回出來
關(guān)于async/await總結(jié)
放在一個(gè)函數(shù)前的async有兩個(gè)作用:
使函數(shù)總是返回一個(gè)promise,允許在這其中使用await
promise前面的await關(guān)鍵字能夠使JavaScript等待,直到promise處理結(jié)束。然后:
如果它是一個(gè)錯(cuò)誤,異常就產(chǎn)生了,就像在那個(gè)地方調(diào)用了throw error一樣。
否則,它會(huì)返回一個(gè)結(jié)果,我們可以將它分配給一個(gè)值
他們一起提供了一個(gè)很好的框架來編寫易于讀寫的異步代碼。
有了async/await,我們很少需要寫promise.then/catch,但是我們?nèi)匀徊粦?yīng)該忘記它們是基于promise的,因?yàn)橛行r(shí)候(例如在最外面的范圍內(nèi))我們不得不使用這些方法。Promise.all也是一個(gè)非常棒的東西,它能夠同時(shí)等待很多任務(wù)。
4、node.js nextTick setImmidate
nextTick vs setImmediate
(1).什么是輪詢
nodejs中是事件驅(qū)動(dòng)的,有一個(gè)循環(huán)線程一直從事件隊(duì)列中取任務(wù)執(zhí)行或者I/O的操作轉(zhuǎn)給后臺(tái)線程池來操作,把這個(gè)循環(huán)線程的每次執(zhí)行的過程算是一次輪詢。
(2).setImmediate()的使用
即時(shí)計(jì)時(shí)器立即執(zhí)行工作,它是在事件輪詢之后執(zhí)行,為了防止輪詢阻塞,每次只會(huì)調(diào)用一個(gè)。
詳情:Window.setImmediate() - Web APIs | MDN
(3).Process.nextTick()的使用
它和setImmediate()執(zhí)行的順序不一樣,它是在事件輪詢之前執(zhí)行,為了防止I/O饑餓,所以有一個(gè)默認(rèn)process.maxTickDepth=1000來限制事件隊(duì)列的每次循環(huán)可執(zhí)行的nextTick()事件的數(shù)目。
詳情:process 進(jìn)程 | Node.js API 文檔
總結(jié):
nextTick()的回調(diào)函數(shù)執(zhí)行的優(yōu)先級(jí)要高于setImmediate()。
process.nextTick()屬于idle觀察者,setImmediate()屬于check觀察者.在每一輪循環(huán)檢查中,idle觀察者先于I/O觀察者,I/O觀察者先于check觀察者。
在具體實(shí)現(xiàn)上,process.nextTick()的回調(diào)函數(shù)保存在一個(gè)數(shù)組中,setImmediate()的結(jié)果則是保存在鏈表中。
在行為上,process.nextTick()在每輪循環(huán)中會(huì)將數(shù)組中的回調(diào)函數(shù)全部執(zhí)行完,而setImmediate()在每輪循環(huán)中執(zhí)行鏈表中的一個(gè)回調(diào)函數(shù)。
5、第三方庫(kù) async.js
async.js是一個(gè)第三方庫(kù),帶有很多api。
暴露了一個(gè)async對(duì)象,這個(gè)對(duì)象身上有很多的api(多任務(wù)執(zhí)行),例如parallel,series等。
//和series/waterfall不同,task是并列執(zhí)行的,callback不是執(zhí)行下一個(gè)task。 async.parallel([ function(callback){ callback(null,'任務(wù)1') }, function(callback){ callback(null,'任務(wù)2') }, ],(err,data)=>{ console.log('data',data) })
//按順序執(zhí)行數(shù)組里的task,沒有調(diào)用到callback不會(huì)執(zhí)行下一個(gè)task async.series([ function(callback){ // do some stuff ... callback(null, 'one'); }, function(callback){ // do some more stuff ... callback(null, 'two'); } ], // optional callback function(err, results){ // results is now equal to ['one', 'two'] });
async.js更多詳情:async-js - npm
web前端
版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實(shí)的內(nèi)容,請(qǐng)聯(lián)系我們jiasou666@gmail.com 處理,核實(shí)后本網(wǎng)站將在24小時(shí)內(nèi)刪除侵權(quán)內(nèi)容。