async與await

async/await的基礎用法

1、async/await的優勢node

1)方便級聯調用:即調用依次發生的場景;python

2)同步代碼編寫方式: Promise使用then函數進行鏈式調用,一直點點點,是一種從左向右的橫向寫法;async/await從上到下,順序執行,就像寫同步代碼同樣,更符合代碼編寫習慣;編程

3)多個參數傳遞: Promise的then函數只能傳遞一個參數,雖然能夠經過包裝成對象來傳遞多個參數,可是會致使傳遞冗餘信息,頻繁的解析又從新組合參數,比較麻煩;async/await沒有這個限制,能夠當作普通的局部變量來處理,用let或者const定義的塊級變量想怎麼用就怎麼用,想定義幾個就定義幾個,徹底沒有限制,也沒有冗餘工做;多線程

4)同步代碼和異步代碼能夠一塊兒編寫: 使用Promise的時候最好將同步代碼和異步代碼放在不一樣的then節點中,這樣結構更加清晰;async/await整個書寫習慣都是同步的,不須要糾結同步和異步的區別,固然,異步過程須要包裝成一個Promise對象放在await關鍵字後面;併發

5)基於協程: Promise是根據函數式編程的範式,對異步過程進行了一層封裝,async/await基於協程的機制,是真正的「保存上下文,控制權切換……控制權恢復,取回上下文」這種機制,是對異步過程更精確的一種描述;異步

6)async/await是對Promise的優化: async/await是基於Promise的,是進一步的一種優化,不過在寫代碼時,Promise自己的API出現得不多,很接近同步代碼的寫法;async

2、協程函數式編程

  • 進程>線程>協程
  • 協程的第一大優點是具備極高的執行效率,由於子程序切換不是線程切換,而是由程序自身控制,所以沒有線程切換的開銷,和多線程比,線程數量越多,協程的性能優點就越明顯;
  • 協程的第二大優點是不須要多線程的鎖機制,由於只有一個線程,也不存在同時寫變量衝突,在協程中控制共享資源不加鎖,只須要判斷狀態就行了,因此執行效率比多線程高不少;
  • 協程看上去也是子程序,但執行過程當中,在子程序內部可中斷,而後轉而執行別的子程序,在適當的時候再返回來接着執行,須要注意的是:在一個子程序中中斷,去執行其餘子程序,這並非函數調用,有點相似於CPU的中斷;
  • 用汽車和公路舉個例子:js公路只是單行道(主線程),可是有不少車道(輔助線程)均可以匯入車流(異步任務完成後回調進入主線程的任務隊列);generator把js公路變成了多車道(協程實現),可是同一時間只有一個車道上的車能開(依然單線程),不過能夠自由變道(移交控制權);
  • 協程意思是多個線程互相協做,完成異步任務,運行流程大體以下:
    1)協程A開始執行;
    2)協程A執行到一半,進入暫停,執行權轉移到協程B;
    3)一段時間後,協程B交還執行權;
    4)協程A恢復執行;
  • 協程是一個無優先級的子程序調度組件,容許子程序在特定的地點掛起恢復;
  • 線程包含於進程,協程包含於線程,只要內存足夠,一個線程中能夠有任意多個協程,但某一個時刻只能有一個協程在運行,多個協程分享該線程分配到的計算機資源;
  • 就實際使用理解來講,協程容許咱們寫同步代碼的邏輯,卻作着異步的事,避免了回調嵌套,使得代碼邏輯清晰;
  • 什麼時候掛起,喚醒協程:協程是爲了使用異步的優點,異步操做是爲了不IO操做阻塞線程,那麼協程掛起的時刻應該是當前協程發起異步操做的時候,而喚醒應該在其餘協程退出,而且他的異步操做完成時;
  • 單線程內開啓協程,一旦遇到io,從應用程序級別(而非操做系統)控制切換對比操做系統控制線程的切換,用戶在單線程內控制協程的切換,優勢以下:
    1)協程的切換開銷更小,屬於程序級別的切換,操做系統徹底感知不到,於是更加輕量級;
    2)單線程內就能夠實現併發的效果,最大限度地利用cpu;
// 傳統的生產者-消費者模型是一個線程寫消息,一個線程取消息,經過鎖機制控制隊列和等待,但一不當心就可能死鎖。 // 若是改用協程,生產者生產消息後,直接經過yield跳轉到消費者開始執行,待消費者執行完畢後,切換回生產者繼續生產,效率極高: import time def consumer(): r = '' while True: n = yield r if not n: return print('[CONSUMER] Consuming %s...' % n) time.sleep(1) r = '200 OK' def produce(c): c.next() n = 0 while n < 5: n = n + 1 print('[PRODUCER] Producing %s...' % n) r = c.send(n) print('[PRODUCER] Consumer return: %s' % r) c.close() if __name__=='__main__': c = consumer() produce(c) 
[PRODUCER] Producing 1... [CONSUMER] Consuming 1... [PRODUCER] Consumer return: 200 OK [PRODUCER] Producing 2... [CONSUMER] Consuming 2... [PRODUCER] Consumer return: 200 OK [PRODUCER] Producing 3... [CONSUMER] Consuming 3... [PRODUCER] Consumer return: 200 OK [PRODUCER] Producing 4... [CONSUMER] Consuming 4... [PRODUCER] Consumer return: 200 OK [PRODUCER] Producing 5... [CONSUMER] Consuming 5... [PRODUCER] Consumer return: 200 OK 

注意到consumer函數是一個generator(生成器),把一個consumer傳入produce後:函數

首先調用c.next()啓動生成器;性能

而後,一旦生產了東西,經過c.send(n)切換到consumer執行;

consumer經過yield拿到消息,處理,又經過yield把結果傳回;

produce拿到consumer處理的結果,繼續生產下一條消息;

produce決定不生產了,經過c.close()關閉consumer,整個過程結束。

整個流程無鎖,由一個線程執行,produce和consumer協做完成任務,因此稱爲「協程」,而非線程的搶佔式多任務。
3、async關鍵字

1)代表程序裏面可能有異步過程: async關鍵字代表程序裏面可能有異步過程,裏面能夠有await關鍵字;固然所有是同步代碼也不要緊,可是這樣async關鍵字就顯得多餘了;

2)非阻塞: async函數裏面若是有異步過程會等待,可是async函數自己會立刻返回,不會阻塞當前線程,能夠簡單認爲,async函數工做在主線程,同步執行,不會阻塞界面渲染,async函數內部由await關鍵字修飾的異步過程,工做在相應的協程上,會阻塞等待異步任務的完成再返回;

3)async函數返回類型爲Promise對象: 這是和普通函數本質上不一樣的地方,也是使用時重點注意的地方;
(1)return newPromise();這個符合async函數本意;
(2)return data;這個是同步函數的寫法,這裏是要特別注意的,這個時候,其實就至關於Promise.resolve(data);仍是一個Promise對象,可是在調用async函數的地方經過簡單的=是拿不到這個data的,由於返回值是一個Promise對象,因此須要用.then(data => { })函數才能夠拿到這個data;
(3)若是沒有返回值,至關於返回了Promise.resolve(undefined);

4)無等待 聯想到Promise的特色,在沒有await的狀況下執行async函數,它會當即執行,返回一個Promise對象,而且絕對不會阻塞後面的語句,這和普通返回Promise對象的函數並沒有二致;

5)await不處理異步error: await是無論異步過程的reject(error)消息的,async函數返回的這個Promise對象的catch函數負責統一抓取內部全部異步過程的錯誤;async函數內部只要有一個異步過程發生錯誤,整個執行過程就中斷,這個返回的Promise對象的catch就能抓取到這個錯誤;

5)async函數的執行: async函數執行和普通函數同樣,函數名帶個()就能夠了,參數個數隨意,沒有限制,也須要有async關鍵字;只是返回值是一個Promise對象,能夠用then函數獲得返回值,用catch抓整個流程中發生的錯誤;

async function testAsync() { return "hello async"; } const result = testAsync(); // 返回一個Promise對象 console.log(result); // async函數返回的是一個Promise對象,async函數(包括函數語句、函數表達式、Lambda表達式)會返回一個Promise對象,若是在函數中return一個直接量,async會把這個直接量經過Promise.resolve() 封裝成 Promise 對象; 
// async函數返回的是一個Promise對象,因此在最外層不能用await獲取其返回值的狀況,應該使用原始的方式:then()鏈來處理這個Promise對象 testAsync().then(v => { console.log(v); // 輸出 hello async }); 

4、await關鍵字

1)await只能在async函數內部使用:不能放在普通函數裏面,不然會報錯;

2)await關鍵字後面跟Promise對象:在Pending狀態時,相應的協程會交出控制權,進入等待狀態,這是協程的本質;

3)await是async wait的意思: wait的是resolve(data)的消息,並把數據data返回,好比下面代碼中,當Promise對象由Pending變爲Resolved的時候,變量a就等於data,而後再順序執行下面的語句console.log(a),這真的是等待,真的是順序執行,表現和同步代碼幾乎如出一轍;

const a = await new Promise((resolve, reject) => { // async process ... return resolve(data); }); console.log(a); 

4)await後面也能夠跟同步代碼: 不過系統會自動將其轉化成一個Promsie對象,好比:

const a = await 'hello world' // 至關於 const a = await Promise.resolve('hello world'); // 跟同步代碼是同樣的,還不如省事點,直接去掉await關鍵字 const a = 'hello world'; 

5)await對於失敗消息的處理: await只關心異步過程成功的消息resolve(data),拿到相應的數據data,至於失敗消息reject(error),不關心不處理;對於錯誤的處理有如下幾種方法供選擇:
(1)讓await後面的Promise對象本身catch;
(2)也可讓外面的async函數返回的Promise對象統一catch;
(3)像同步代碼同樣,放在一個try...catch結構中;

async componentDidMount() { // 這是React Native的回調函數,加個async關鍵字,沒有任何影響,可是能夠用await關鍵字 // 將異步和同步的代碼放在一個try..catch中,異常都能抓到 try { let array = null; let data = await asyncFunction(); // 這裏用await關鍵字,就能拿到結果值;不然,沒有await的話,只能拿到Promise對象 if (array.length > 0) { // 這裏會拋出異常,下面的catch也能抓到 array.push(data); } } catch (error) { alert(JSON.stringify(error)) } } 

6)await對於結果的處理: await是個運算符,用於組成表達式,await表達式的運算結果取決於它等的東西,若是它等到的不是一個Promise對象,那麼await表達式的運算結果就是它等到的東西;若是它等到的是一個Promise對象,await就忙起來了,它會阻塞其後面的代碼,等着Promise對象resolve,而後獲得resolve的值,做爲await表達式的運算結果;雖然是阻塞,但async函數調用並不會形成阻塞,它內部全部的阻塞都被封裝在一個Promise對象中異步執行,這也正是await必須用在async函數中的緣由;

5、套路分析一

// 異步過程封裝 function sleep(ms) { return new Promise((resolve) => { setTimeout(() => { resolve('sleep for ' + ms + ' ms'); }, ms); }); } 
// 定義異步流程,能夠將按照須要定製,就像寫同步代碼那樣 async function asyncFunction() { console.time('asyncFunction total executing:'); const sleep1 = await sleep(2000); console.log('sleep1: ' + sleep1); const [sleep2, sleep3, sleep4]= await Promise.all([sleep(2000), sleep(1000), sleep(1500)]); console.log('sleep2: ' + sleep2); console.log('sleep3: ' + sleep3); console.log('sleep4: ' + sleep4); const sleepRace = await Promise.race([sleep(3000), sleep(1000), sleep(1000)]); console.log('sleep race: ' + sleepRace); console.timeEnd('asyncFunction total executing:'); return 'asyncFunction done.' // 這個能夠不返回,這裏只是作個標記,爲了顯示流程 } 
// 像普通函數調用async函數,在then函數中獲取整個流程的返回信息,在catch函數統一處理出錯信息 asyncFunction().then(data => { console.log(data); // asyncFunction return 的內容在這裏獲取 }).catch(error => { console.log(error); // asyncFunction 的錯誤統一在這裏抓取 }); console.log('after asyncFunction code executing....'); // 這個表明asyncFunction函數後的代碼, // 顯示asyncFunction自己會當即返回,不會阻塞主線程 
// 執行結果
after asyncFunction code executing....
sleep1: sleep for 2000 ms
sleep2: sleep for 2000 ms
sleep3: sleep for 1000 ms
sleep4: sleep for 1500 ms
sleep race: sleep for 1000 ms
asyncFunction total executing:: 5006.276123046875ms
asyncFunction done.

代碼分析

  • after asyncFunction code executing....代碼位置在async函數asyncFunction()調用以後,反而先輸出,這說明async函數asyncFunction()調用以後會立刻返回,不會阻塞主線程;

  • sleep1: sleep for 2000 ms這是第一個await以後的第一個異步過程,最早執行,也最早完成,說明後面的代碼,不管是同步和異步,都在等他執行完畢;

  • sleep2 ~ sleep4這是第二個await以後的Promise.all()異步過程,這是「比慢模式」,三個sleep都完成後,再運行下面的代碼,耗時最長的是2000ms;

  • sleep race: sleep for 1000 ms這是第三個await以後的Promise.race()異步過程,這是「比快模式」,耗時最短sleep都完成後,就運行下面的代碼,耗時最短的是1000ms;

  • asyncFunction total executing:: 5006.276123046875ms這是最後的統計總共運行時間代碼,三個await以後的異步過程之和:
    1000(獨立的) + 2000(Promise.all) + 1000(Promise.race) = 5000ms
    這個和統計出來的5006.276123046875ms很是接近,說明上面的異步過程,和同步代碼執行過程一致,協程真的是在等待異步過程執行完畢;

  • asyncFunction done.這個是async函數返回的信息,在執行時的then函數中得到,說明整個流程完畢以後參數傳遞的過程;

6、套路分析二

/** * 傳入參數 n,表示這個函數執行的時間(毫秒) * 執行的結果是 n + 200,這個值將用於下一步驟 */ function takeLongTime(n) { return new Promise(resolve => { setTimeout(() => resolve(n + 200), n); }); } function step1(n) { console.log(`step1 with ${n}`); return takeLongTime(n); } function step2(n) { console.log(`step2 with ${n}`); return takeLongTime(n); } function step3(n) { console.log(`step3 with ${n}`); return takeLongTime(n); } 
// Promise方式調用 function doIt() { console.time("doIt"); const time1 = 300; step1(time1) .then(time2 => step2(time2)) .then(time3 => step3(time3)) .then(result => { console.log(`result is ${result}`); console.timeEnd("doIt"); }); } doIt(); // c:\var\test>node --harmony_async_await . // step1 with 300 // step2 with 500 // step3 with 700 // result is 900 // doIt: 1507.251ms 
// async/await方式調用 async function doIt() { console.time("doIt"); const time1 = 300; const time2 = await step1(time1); const time3 = await step2(time2); const result = await step3(time3); console.log(`result is ${result}`); console.timeEnd("doIt"); } doIt(); 

7、套路分析三

function step1(n) { console.log(`step1 with ${n}`); return takeLongTime(n); } function step2(m, n) { console.log(`step2 with ${m} and ${n}`); return takeLongTime(m + n); } function step3(k, m, n) { console.log(`step3 with ${k}, ${m} and ${n}`); return takeLongTime(k + m + n); } 
// Promise方式調用 function doIt() { console.time("doIt"); const time1 = 300; step1(time1) .then(time2 => { return step2(time1, time2) .then(time3 => [time1, time2, time3]); }) .then(times => { const [time1, time2, time3] = times; return step3(time1, time2, time3); }) .then(result => { console.log(`result is ${result}`); console.timeEnd("doIt"); }); } doIt(); 
// async/await方式調用 async function doIt() { console.time("doIt"); const time1 = 300; const time2 = await step1(time1); const time3 = await step2(time1, time2); const result = await step3(time1, time2, time3); console.log(`result is ${result}`); console.timeEnd("doIt"); } doIt(); // c:\var\test>node --harmony_async_await . // step1 with 300 // step2 with 800 = 300 + 500 // step3 with 1800 = 300 + 500 + 1000 // result is 2000 // doIt: 2907.387ms
相關文章
相關標籤/搜索