其他章節請看:
async
前文我們已經知道 promise 是一種非同步程式設計的選擇。而 async 是一種用於執行非同步任務更簡單的語法。
Tip:建議學完 Promise 在看本文。
async 函式
async 函式是使用 async 關鍵字宣告的函式。就像這樣:
async function fa(){
}
async 函式可以看作由多個非同步操作包裝成的一個 Promise 物件。
async 函式返回 Promise
async 函式總是返回一個 Promise 物件。如果一個 async 函式的返回值看起來不是 promise,那麼它將會被隱式地包裝在一個 promise 中。請看示例:
async function fa() {
return 1
}
// 等價於
function fa() {
return Promise.resolve(1)
}
console.log( fa() instanceof Promise) // true
即使 async 方法中沒有顯示的 return ,async 方法仍會返回 Promise。請看示例:
async function fa() {}
console.log( fa() instanceof Promise)
fa 方法等價於:
function fa() {
return Promise.resolve()
}
async 函式多種形式
async 函式有多種使用形式。例如:
// 函式表示式
const fa = async funciton() {};
// 物件的方法
let obj = {async foo(){}}
// Class 的方法
class Dog{
async say(){}
}
// 箭頭函式
const fa = async () => {}
形式雖然很多,但都是在函式前面增加 async 關鍵字。
async 函式中的 return
async 函式內的 return 返回值,會成為 then() 方法回撥函式的引數。請看示例:
async function foo() {
return 'hello'
}
foo().then(v => {
console.log(v)
})
// hello
async 函式內部丟擲的錯誤會導致返回的 Promise 物件變為 reject 狀態。丟擲的錯誤物件會被 catch 方法回撥接收到。請看示例:
async function foo() {
throw new Error('fail')
return 'hello'
}
foo().catch(v => {
console.log(v.message)
})
// fail
Promise 物件的狀態變化
async 函式返回的 Promise 物件必須等到內部所有 await 命令後的 Promise 物件執行完才會發生狀態變化,除非遇到 return 語句,或者丟擲錯誤才會立刻結束。請看示例:
function createPromise(val, time = 1000){
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log(val)
resolve(val)
}, time)
})
}
async function foo() {
let a = await createPromise(1)
let b = await createPromise(2)
return {a, b}
}
foo().then(v => {
console.log(v)
}, v => {
console.log(v.message)
})
/*
1
2
{ a: 1, b: 2 }
*/
這段程式碼需要 2 秒,等待內部兩個 Promise 狀態都置為已完成,才會輸出 { a: 1, b: 2 }
。
如果遇到 return 或者丟擲錯誤,則會立即結束。就像這樣:
async function foo() {
// 遇到 return
return 1
// 或丟擲錯誤
// throw new Error('fail')
let a = await createPromise(1)
let b = await createPromise(2)
return {a, b}
}
await
asyn 函式可能包含 0 個或多個 await 表示式。就像這樣:
async function fa() {
return await 1
}
await 表示式會暫停整個 async 函式的執行程式並出讓其控制權,只有當其等待的基於 promise 的非同步操作被兌現或被拒絕之後才會恢復程式。promise 的解決值會被當作該 await 表示式的返回值。
await 的返回值
首先看一段程式碼:
async function fa() {
const result = await 1
return result
}
fa().then(v => {
console.log(v)
})
// 1
為什麼 result 是 1?
首先,因為 await 命令後面是一個 Promise 物件。如果不是,會被轉為一個立即 resolve 的 Promise 物件。所以下面 fa() 方法是相等的:
async function fa() {
return await 1
}
// 等價於
async function fa() {
return await Promise.resolve(1)
}
而在 Promise 中所學,我們知道 fa() 方法又等於如下程式碼:
async function foo() {
return await new Promise((resolve, reject) => {
resolve(1)
})
}
其次,Promise 的解決值會被當作該 await 表示式的返回值。所以 result 等於 1。
如果刪除 fa() 方法中的 return,將輸出 undefined。請看示例:
async function fa() {
// 刪除 return
await 1
}
fa().then(v => {
console.log(v)
})
// undefined
reject 中斷 async 函式
await 命令後的 Promise 物件如果變成 reject 狀態,則 reject 的引數會被 catch 方法回撥函式接收。就像這樣:
async function foo() {
await Promise.reject(1) // {1}
}
foo().then(v => {
console.log(v)
}).catch(v => {
console.log(`catch, ${v}`)
})
// catch, 1
請注意,await 語句(行{1})前面沒有 return 語句,但是 reject() 方法的引數依然傳入了 catch 方法的回撥函式中,這點與 resolve 狀態不相同。
只要一個 await 語句後面的 Promise 變成 reject,那麼整個 async 函式都會中斷。請看示例:
async function foo() {
await Promise.reject(1)
await new Promise((resolve, reject) => {
console.log(2)
resolve()
})
return 3
}
foo().then(v => {
console.log(v)
}).catch(v => {
console.log(`catch, ${v}`)
})
// catch, 1
由於第一個 await 後面的 Promise 變成 reject,整個 async 函式就中斷執行。
如果我們希望前一個非同步操作失敗,也不中斷後面的非同步操作,可以這麼寫:
try{
await Promise.reject(1)
}catch(e){
}
// 亦或者
// 在 Promise 一文中提到拒絕處理程式能恢復整條鏈的執行
await Promise.reject(1).catch(() => {})
...
如果 await 後面的非同步操作出錯,那麼等同於 async 函式返回的 Promise 物件被 reject。就像這樣:
async function foo() {
await new Promise((resolve, reject) => {
throw new Error('fail')
})
return 3
}
foo().then(v => {
console.log(v)
}).catch(v => {
console.log(`catch, ${v}`)
})
// catch, Error: fail
防止出錯的方法也是將其放在 try ... catch 方法中。下面例子使用 try...catch 實現多次嘗試:
function request(v){
return new Promise((resolve, reject) => {
if(v == 2){
console.log(`resolve${v}`)
resolve(v)
}else{
console.log(`fail${v}`)
throw new Error('fail')
}
})
}
async function foo() {
for(let i = 0; i < 5; i++){
try{
await request(i)
break
}catch(e){} // {1}
}
return 'end'
}
foo().then(v => {
console.log(v)
}).catch(v => {
console.log(`catch, ${v}`)
})
// fail0 fail1 resolve2 end
這段程式碼,如果 await 操作成功,則會使用 break 語句退出迴圈;如果失敗,則會被 catch(行{1}) 捕獲,然後進入下一輪迴圈。
await 與並行
下面的程式碼,會依次輸出 1 和 2,屬於序列。
function createPromise(val, time = 1000){
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log(val)
resolve(val)
}, time)
})
}
async function foo() {
let a = await createPromise(1)
let b = await createPromise(2)
}
foo()
// 1 2
如果多個非同步操作不存在繼發關係,最好讓它們同時觸發。將 foo() 方法改為下面任一方式:
// 方式一
async function foo() {
let p1 = createPromise(1)
let p2 = createPromise(2)
// 至此,兩個非同步操作都已經發出
await p1
await p2
}
// 方式二
async function foo() {
let [p1, p2] = await Promise.all([createPromise(1), createPromise(2)])
}
再次執行,只需要 1 秒就會同時輸出 1 2。
async 函式中的 await
await 關鍵字只能用在 async 函式中。請看示例:
async function fa(){
let arr = [1, 2, 3]
arr.forEach(v => {
await v
})
}
// SyntaxError: await is only valid in async function
這段程式碼將報語法錯誤。
如果將 forEach 方法的引數改為 async 函式,就像這樣:
function createPromise(val, time = 1000){
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log(val)
resolve(val)
}, time)
})
}
async function fa(){
let arr = [1, 2, 3]
// 改為 async 函式
arr.forEach(async v => {
await createPromise(v)
})
}
fa()
// 1 2 3
等待 1 秒後同時輸出 1 2 3。因為這 3 個非同步操作是併發執行。
如果希望多個請求併發執行,也可以使用 Promise.all 方法。就像這樣:
// 替換 fa() 方法即可
async function fa(){
let arr = [1, 2, 3]
let promises = arr.map(v => createPromise(v))
let results = await Promise.all(promises)
console.log(results)
}
而如果需要繼發,可以採用 for 迴圈:
// 替換 fa() 方法即可
async function fa(){
let arr = [1, 2, 3]
// 將 forEach 改為 for 迴圈
for(let i = 0; i < arr.length; i++){
await createPromise(arr[i])
}
}
每過一秒,會依次輸出 1 2 3。
其他章節請看: