JavaScript非同步處理的那些事兒
前言
之前總結了關於 JavaScript 非同步的 事件迴圈與訊息佇列 機制以及 ES6 帶來的 微任務與巨集任務 的知識。傳送門
下面是關於JS非同步處理的各種方案:
callback >> ES6 Primise >> async/await
複製程式碼
沒有非同步處理
先看一段程式碼:
// 假設有一個耗時的非同步請求 ajax,在 2 秒後列印日誌
function ajax () {
// do something...
setTimeout(() => {
console.log('Hello, Zavier Tang!')
}, 2000 )
}
ajax()
// do something...
console.log('The end.')
// The end.
// Hello, Zavier Tang!
複製程式碼
這裡模擬了一個簡單的非同步網路請求,並在 2 秒後列印 "Hello, Zavier Tang!",然後做一些處理("do something")後,列印結束 "The end."。
結果是先列印的 "The end."。
顯然這並不是我們要的結果,"非同步請求" ajax
中的 setTimeout
並沒有阻塞程式碼的執行,而是直接執行了 console.log()
。
非同步的解決方案
1. 傳統(可怕)的 callback 回撥地獄
同樣是上面的非同步網路請求,這裡使用 callback 回撥函式的方式解決 JavaScript 同步帶來的問題。
function ajax (fn) {
// do something...
setTimeout(() => {
console.log('Hello, Zavier Tang!')
fn()
}, 2000)
}
ajax(() => {
// do something...
console.log('The end.')
})
// Hello, Zavier Tang!
// The end.
複製程式碼
這裡我們直接把非同步請求之後要做的一些操作做為回撥函式,傳遞到 ajax 中去,並在非同步請求結束後執行回撥函式裡的操作。
問題似乎已經解決了??但是,如果有多個非同步請求,並且在每個非同步請求完成後都執行一些操作,那程式碼就會像下面這樣:
function ajax (fn) {
// do something...
setTimeout(() => {
console.log('Hello, Zavier Tang!')
fn()
}, 500)
}
ajax(() => {
// do something...
console.log('The end 1.')
ajax(() => {
// do something...
console.log('The end 2.')
ajax(() => {
// do something...
console.log('The end 3.')
ajax(() => {
// do something...
console.log('The end 4.')
ajax(() => {
// do something...
console.log('The end 5.')
// ......
// ......
})
})
})
})
})
// Hello, Zavier Tang!
// The end 1.
// Hello, Zavier Tang!
// The end 2.
// Hello, Zavier Tang!
// The end 3.
// Hello, Zavier Tang!
// The end 4.
// Hello, Zavier Tang!
// The end 5.
複製程式碼
看起來很嚇人!!
如果是這樣:請求1結束後進行請求2,然後還有請求3、請求4、請求5,並且請求2還可能依賴於請求1的資料,請求3依賴於請求2的資料,不停的一層一層巢狀,對於錯誤的處理也極不方便,所以稱之為 callback 回撥地獄。
2. 下一代非同步解決方案:Promise
ES6 的 Promise 被稱為 JS 非同步的下一代解決方案。
關於 Promise:
- 用於非同步計算。
- 可以將非同步操作佇列化,按照期望的順序執行,返回符合預期的結果。
- 可以在物件之間傳遞和操作 Promise,方便處理佇列。
接下來使用 Promise 處理非同步:
function ajax (word) {
return new Promise((resolve) => {
// do something...
setTimeout(() => {
resolve('Hello ' + word)
}, 500)
})
}
ajax('請求1')
.then((word) => {
console.log(word)
console.log('The end 1.')
return ajax('請求2')
})
.then((word) => {
console.log(word)
console.log('The end 2.')
return ajax('請求3')
})
.then((word) => {
console.log(word)
console.log('The end 3.')
})
// .catch(() => {})
// Hello 請求1
// The end 1.
// Hello 請求2
// The end 2.
// Hello 請求3
// The end 3.
複製程式碼
上面還是連續的非同步請求,每次請求後列印日誌。這樣看起來比上面的回撥地獄舒服多了,每個請求通過鏈式的呼叫,在最後可以對所有的請求進行錯誤處理。
但,似乎還並不是特別優雅。
3. 終極解決方案:async/await
關於 async/await 的簡介:
- async/await 是寫非同步程式碼的新方式,不同於以前的 callback 回撥函式和 Promise。
- async/await 是基於 Promise 實現的,不能用於普通的回撥函式。
- async/await 與 Promise 一樣,是非阻塞的。
- async/await 使得非同步程式碼看起來像同步程式碼。
體驗一下神奇的 async/await:
// 接上面的程式碼
async function doAsync () {
const word1 = await ajax('請求1')
console.log(word1)
console.log('The end 1')
const word2 = await ajax('請求2')
console.log(word2)
console.log('The end 2')
const word3 = await ajax('請求3')
console.log(word3)
console.log('The end 3')
}
doAsync()
// Hello 請求1
// The end 1
// Hello 請求2
// The end 2
// Hello 請求3
// The end 3
複製程式碼
這樣看起來,更優雅了。沒有任何括號,也沒有 callback,沒有 then,直接申明 async,使用 await 等待非同步執行完成,看起來也更像是同步的程式碼。
總結
JavaScript 的非同步編寫方式,從 callback 回撥函式到 Promise ,再到 async/await,只能說。。。
技術發展太快啦,趕緊學習吧!