什么是異步
同步(sync)是一件事一件事的執行,只有前一個任務執行完畢才能執行后一個任務。異步(async)相對於同步,程序無須按照代碼順序自上而下的執行。
為什么要使用異步
由於js是單線程的,只能在js引擎的主線程上運行,所以js代碼只能一行一行的執行,如果沒有異步的存在,由於當前的任務還沒有完成,其他的所有操作都會無響應,用戶就會長時間的在等待。
JS常見的異步模式
常見的異步模式有六種:
- 回調函數
- 事件監聽
- 發布/訂閱模式
- promise
- Generator(ES6)
- async/await(ES7)
回調函數
回調函數是異步操作最基本的方法。
回調函數作為參數傳遞給另一個函數,在另一個函數中被調用。常見的回調函數的例子:
ajax(url, () => {
//處理邏輯
})
但是使用回調函數,經常會寫出回調地獄,這是非常致命的。
回調地獄的根本問題是:
- 嵌套函數存在耦合性
- 嵌套函數變多,處理問題的困難也變大
事件監聽
事件監聽模式,異步任務的執行取決於,某個事件的發生。比如點擊事件(onClick)和內容改變時間(onChange)等。
發布/訂閱模式
在發布/訂閱模式中,想象有一個類似消息中心的地方,可以在消息中心“注冊”一條消息,然后就會有若干對這消息感興趣的人“訂閱”,一旦該消息被“發布”,所有”訂閱“了該消息的用戶都會得到提醒。
Promise
Promise是ES6推出的一種解決異步編程的解決方案。Promise是承諾的意思,這個承諾在未來會有一個確定的答復,該承諾有三種狀態:等待中(pending)、完成了(resolved)、拒絕了(rejected)。一旦狀態從等待改變為其他狀態就不再可變了。
Promise是個構造函數,接受一個函數作為參數。作為參數的函數有兩個參數:resolve和reject,分別對應完成和拒絕兩種狀態。我們可以選擇在不同時候執行resolve或reject去觸發下一個動作,執行then方法里的函數。
ajax(rul)
.then(res => {
console.log(res)
return ajax(url1)
}).then(res => {
console.log(res)
return ajax(url2)
}).then(res => console.log(res))
Promise實現了鏈式調用,每次調用then之后返回的都是一個Promise對象,如果在then使用了return,return返回的值會被Promise.resolve()
包裝。
Generator
Generator是一種特殊的函數,有以下特點:
- 聲明時需要在function后面加上*,並且配合函數里面yield關鍵字使用。
- 在執行Generator函數的時候,會返回一個Iterator遍歷器對象,通過其next方法,將Generator內的代碼以yield為分界分步執行。
- 執行Generator函數時,代碼不會執行,而是需要調用Iterator遍歷器對象的next方法,這時程序才會執行從頭或從上一個yield到下一個yield或return或函數體尾部之間的代碼,並將yield后面的值包裝成json對象返回。
- value取的yield或return后面的值,否則就是undefined,done的值如果碰到return或者執行完函數體會返回true,否則就會返回false。
Async/Await
一個函數如果加上async,那么該函數就會返回一個Promise對象。
async function test() {
console.log('1')
}
console.log(test) // Promise {<resolve>: "1"}
async就是將函數返回使用Promise.resolve()
,和then
處理返回值一樣,await只能配套async使用。但如果多個異步代碼沒有依賴性卻使用了await會導致性能降低。
async在使用上會有一些需要注意的地方:
- async函數的返回值是一個Promise對象,不像是generator函數返回的是Iterator遍歷器對象,所以async函數執行后可以繼續使用then等方法來繼續執行后面的邏輯。
- await后邊一般跟Promise對象,async函數執行遇到await后,等待后面的Promise對象的狀態從pending變成resolve后,將resolve的參數返回並自動往下執行知道下一個await或結束。
- await后面也可以跟一個async進行嵌套使用。