相容性
所有瀏覽器已經支援
使用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物件中的值的方法有兩種:
-
.then() 方法
let p = Promise.resolve()p.then(function(x) {
console.log(x)
})複製程式碼 -
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 的宣告與呼叫
- 如果一個函式通過async來宣告,則一定可以通過await 關鍵字來取到該函式的返回值。
- 如果一個函式通過async來宣告,則一定也可以通過.then() 方法來取到該函式返回的 promise中的值(因為return出來的結果一定是promise物件)
- 如果一個函式沒有通過async 來宣告,但只要return 出現的是promise物件 ,則也可以通過await來拿到promise裡面的取值。
- 如果一個函式沒有通過async 來宣告,但只要return 出來一個promise ,也可以通過.then()拿到promise裡面值(在沒有async/await 的年代就是這樣做的)
- 如果一個函式通過async宣告,則在該函式內部可以使用await ,也可以使用.then()
- 如何一個函式沒有通過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關鍵字後,流程如下圖所:
遇到函式內的await時,會發起非同步呼叫,推入非同步任務佇列,然後node會接著執行該函式被呼叫的那一行的下面的程式碼。
所以,await
關鍵字不會阻塞node的event_loop的執行緒。當程式碼執行到 async 函式遇到await 關鍵詞時,不會繼續往下執行,而是等待非同步的結果,但是此時node執行緒並不會閒到無所事事,而是繼續執行async 函式被呼叫的那一行下面的程式碼。等到非同步操作的結果發生了變化時,將非同步結果推入任務佇列,event_loop從佇列中取出事件,推入到執行棧中。
await 真的會讓程式碼變慢!
在上文中,await關鍵字不會阻塞node的event_loop的執行緒,那麼,await 是不是可以放心大膽的時候了呢?
並不是!
我等得花兒都謝了
在上面的程式碼中,requestsAsync 這個方法依次呼叫了多個await 方法。
雖然並不會阻塞event_loop執行緒:requestAsync 方法被呼叫的那一行下面的程式碼會被執行。
但是在requestAsync 方法內部其實造成了阻塞:在呼叫第一行非同步請求時,會把這個推入到非同步佇列,然後暫停執行。等第一行非同步請求有了結果之後,呼叫第二行非同步請求時,接著會把這個推入到非同步佇列,然後再暫停執行的……
所以在 requestAsync 方法內部,每一行非同步請求,都需要苦苦等待它上面的await請求結束後,才可以去發起請求,最終執行的時間消耗了1443ms
序列變並行
如果上面這種序列非同步請求之間,沒有依賴關係,建議修改成並行的結構。
也就是如下所示:
上文中提到,async 非同步方法也可以不加 await 關鍵詞呼叫,返回的結果會是一個處於pendding 狀態的promise物件。於是,上面程式碼中 rp非同步方法不加 await 關鍵詞呼叫,會返回一個個處於pendding 狀態的promise物件。
因為沒有await關鍵字,程式碼不會被暫停執行,所有的非同步請求會被依次觸發。
等待 Promise.all() 捕捉到所有的pendding 狀態的promise物件的改變,可以拿到各個非同步請求的結果。
採用這種請求的總耗時是:
參考資料: