深入理解 async / await

杜俊成要好好學習發表於2018-09-13

相容性

所有瀏覽器已經支援

使用async宣告函式

使用async宣告的函式,和普通函式唯一的區別在於,返回值的不同。

返回值是promise

這是使用async宣告的函式,最常規的用法

const request = require('request');
async function f1() {
return new Promise(function(resolve, reject) {
request('http://www.baidu.com',function(err, res, body) {
resolve(body)
})
})
}(async function() {
console.log(f1());

})()複製程式碼

返回值是普通值

如果return 出來一個普通值,會被包裝成一個promise物件。該promise狀態為fullfilled, 該promise的值為該簡單值。可以使用.then() 方法取到該promise物件的值(該值就是async宣告的函式返回來的簡單值)

async function f1 () { 
return 10;

}console.log(f1());
// Promise 
{<
resolved>
: 10
}
fn1().then(function (x) {
console.log(x);
// 10
})複製程式碼

返回值為Error型別

如果return出來是一個Error型別,則同樣會被包裝成一個promise物件,該promise物件的狀態是reject, 值是Error的資訊

async function f1 () { 
return new Error('報錯了');

}console.log(f1()) // Promise 
{<
rejected>
: Error: 報錯了
複製程式碼

如何想取出來該promise的報錯資訊,可以通過.then的第二個引數,或者通過.catch 方法

async function f1() { 
throw new Error('ssss');

}// 方法1f1().then(function(x){
console.log(x)
}, function(e) {
console.log(e)
})// 方法2f1().catch(function(e){
console.log(e)
})複製程式碼

沒有返回值

如果沒有return任何東西,則同樣會返回一個promise物件。該promise物件的狀態為fullfilled,該promsie的值為undefined.

async function f1 () { 
//do nothing
}console.log(f1());
// Promise 
{<
resolved>
: undefined
}
複製程式碼
const rp = require('request-promise');
async function f1() {
await rp('http://www.beibei.com');

}(async () =>
{
console.log(await f1());
// undefined
})()複製程式碼

使用await 取到 promise 物件中的值

await 關鍵字可以取到 promise 物件中的值,所以,目前取出promise物件中的值的方法有兩種:

  1. .then() 方法

    let p = Promise.resolve()p.then(function(x) { 
    console.log(x)
    })複製程式碼
  2. await 關鍵字

    var p1 = Promise.resolve(1);
    (async function() {
    console.log(await p1);

    })()複製程式碼

需要注意的是,await 關鍵字只能在 async 宣告的函式中使用。(這也是為什麼上面的程式碼要放在 async 匿名的自執行函式裡面)

await 最主要的作用是代替 .then 方法:

// 使用 .then 方法asycn function asyncFunc() { 
otherAsyncFunc().then(function(result) {
console.log(result)
})
}// 使用 await 關鍵字async function asyncFunc() {
const result = await otherAsyncFunc();
console.log(result);

}複製程式碼

當串聯非同步的操作時,await 要比.then方法更加簡潔

// 使用 .then 進行串聯操作function asyncFunc() { 
otherAsyncFunc1().then(function(x){
console.log(x) return otherAsyncFunc2();

}).then(function(x) {
console.log(x)
})
}// 使用await關鍵字async function asyncFunc() {
const result1 = await otherAsyncFunc1();
console.log(result1);
const result2 = await otherAsyncFunc2();
console.log(result2);

}複製程式碼

當進行併發非同步操作的時候,

const request           = require('request');
const rp = require('request-promise');
// 使用 .then 方法function fn1() {
let p1 = rp('http://www.baidu.com');
let p2 = rp('http://www.baidu.com');
Promise.all([p1, p2]).then(function([res1, res2]) {
console.log(res1) console.log(res2)
})
}// 使用await 關鍵字async function fn1() {
let p1 = rp('http://www.baidu.com');
let p2 = rp('http://www.baidu.com');
let [res1, res2] = await Promise.all([p1, p2]);
console.log(res1) console.log(res2)
}複製程式碼

當處理 異常時, 採用 await 關鍵字是使用try……catch, 而另一種則是使用 .catch()

async function asyncFunc() { 
try {
await otherAsyncFunc();

} catch (err) {
console.error(err);

}
}// Equivalent to:function asyncFunc() {
return otherAsyncFunc() .catch(err =>
{
console.error(err);

});

}複製程式碼

await 關鍵字會讓程式碼執行到 await 這一行的時候,“暫停執行”,等到非同步的操作有了結果,再繼續往下執行。

async 與 await 的宣告與呼叫

  1. 如果一個函式通過async來宣告,則一定可以通過await 關鍵字來取到該函式的返回值。
  2. 如果一個函式通過async來宣告,則一定也可以通過.then() 方法來取到該函式返回的 promise中的值(因為return出來的結果一定是promise物件)
  3. 如果一個函式沒有通過async 來宣告,但只要return 出現的是promise物件 ,則也可以通過await來拿到promise裡面的取值。
  4. 如果一個函式沒有通過async 來宣告,但只要return 出來一個promise ,也可以通過.then()拿到promise裡面值(在沒有async/await 的年代就是這樣做的)
  5. 如果一個函式通過async宣告,則在該函式內部可以使用await ,也可以使用.then()
  6. 如何一個函式沒有通過async 宣告,則在該函式內部不可以使用await,但是可以使用 .then()

await 會阻塞執行緒嗎?

await 關鍵字只能用在 async 方法裡面,當執行到 await 關鍵字時,程式碼會“暫停”執行,等到非同步的方法返回了結果後,才會繼續執行。那麼,問題來了,await關鍵字會阻塞執行緒嗎?

不會,本質是. then()的語法糖

await 並沒有改變node的單執行緒的本質,沒有改變event_loop的模型,只是方便我們寫程式碼,更快捷,更清晰。

await foo();
// foo is an async function that returns a promiseconsole.log("hello");
複製程式碼

上面的程式碼,執行到foo時會”暫停“,等待結果,然後才執行console.log,本質上是下面程式碼的語法糖:

foo().then(() =>
{
console.log("hello");

});
複製程式碼

await 關鍵字是把呼叫函式下面的邏輯,放入到一個隱形的,看不見的.then()中,讓我們的程式碼看起來是同步執行的。

node的單執行緒在遇到await關鍵字後,流程如下圖所:

深入理解 async / await

遇到函式內的await時,會發起非同步呼叫,推入非同步任務佇列,然後node會接著執行該函式被呼叫的那一行的下面的程式碼

所以,await關鍵字不會阻塞node的event_loop的執行緒。當程式碼執行到 async 函式遇到await 關鍵詞時,不會繼續往下執行,而是等待非同步的結果,但是此時node執行緒並不會閒到無所事事,而是繼續執行async 函式被呼叫的那一行下面的程式碼。等到非同步操作的結果發生了變化時,將非同步結果推入任務佇列,event_loop從佇列中取出事件,推入到執行棧中。

await 真的會讓程式碼變慢!

在上文中,await關鍵字不會阻塞node的event_loop的執行緒,那麼,await 是不是可以放心大膽的時候了呢?

並不是!

我等得花兒都謝了

深入理解 async / await

在上面的程式碼中,requestsAsync 這個方法依次呼叫了多個await 方法。

深入理解 async / await

雖然並不會阻塞event_loop執行緒:requestAsync 方法被呼叫的那一行下面的程式碼會被執行。

但是在requestAsync 方法內部其實造成了阻塞:在呼叫第一行非同步請求時,會把這個推入到非同步佇列,然後暫停執行。等第一行非同步請求有了結果之後,呼叫第二行非同步請求時,接著會把這個推入到非同步佇列,然後再暫停執行的……

所以在 requestAsync 方法內部,每一行非同步請求,都需要苦苦等待它上面的await請求結束後,才可以去發起請求,最終執行的時間消耗了1443ms

序列變並行

如果上面這種序列非同步請求之間,沒有依賴關係,建議修改成並行的結構。

也就是如下所示:

image-20180912154519600

上文中提到,async 非同步方法也可以不加 await 關鍵詞呼叫,返回的結果會是一個處於pendding 狀態的promise物件。於是,上面程式碼中 rp非同步方法不加 await 關鍵詞呼叫,會返回一個個處於pendding 狀態的promise物件。

因為沒有await關鍵字,程式碼不會被暫停執行,所有的非同步請求會被依次觸發。

等待 Promise.all() 捕捉到所有的pendding 狀態的promise物件的改變,可以拿到各個非同步請求的結果。

採用這種請求的總耗時是:

image-20180912155253460

參考資料:

exploringjs.com/es2016-es20…

2ality.com/2016/10/asy…

來源:https://juejin.im/post/5b99cbe35188255c930dc74c

相關文章