js中非同步方案比較完整版(callback,promise,generator,async)

  • 2019 年 10 月 4 日
  • 筆記

JS 非同步已經告一段落了,這裡來一波小總結

1. 回調函數(callback)

setTimeout(() => {      // callback 函數體  }, 1000)

缺點:回調地獄,不能用 try catch 捕獲錯誤,不能 return

回調地獄的根本問題在於:

  • 缺乏順序性: 回調地獄導致的調試困難,和大腦的思維方式不符
  • 嵌套函數存在耦合性,一旦有所改動,就會牽一髮而動全身,即(控制反轉
  • 嵌套函數過多的多話,很難處理錯誤
ajax('XXX1', () => {      // callback 函數體      ajax('XXX2', () => {          // callback 函數體          ajax('XXX3', () => {              // callback 函數體          })      })  })

優點:解決了同步的問題(只要有一個任務耗時很長,後面的任務都必須排隊等著,會拖延整個程式的執行。)

2. Promise

Promise就是為了解決callback的問題而產生的。

Promise 實現了鏈式調用,也就是說每次 then 後返回的都是一個全新 Promise,如果我們在 then 中 return ,return 的結果會被 Promise.resolve() 包裝

優點:解決了回調地獄的問題

ajax('XXX1')    .then(res => {        // 操作邏輯        return ajax('XXX2')    }).then(res => {        // 操作邏輯        return ajax('XXX3')    }).then(res => {        // 操作邏輯    })

缺點:無法取消 Promise ,錯誤需要通過回調函數來捕獲

3. Generator

特點:可以控制函數的執行,可以配合 co 函數庫使用

function *fetch() {      yield ajax('XXX1', () => {})      yield ajax('XXX2', () => {})      yield ajax('XXX3', () => {})  }  let it = fetch()  let result1 = it.next()  let result2 = it.next()  let result3 = it.next()

4. Async/await

async、await 是非同步的終極解決方案

優點是:程式碼清晰,不用像 Promise 寫一大堆 then 鏈,處理了回調地獄的問題

缺點:await 將非同步程式碼改造成同步程式碼,如果多個非同步操作沒有依賴性而使用 await 會導致性能上的降低。

async function test() {    // 以下程式碼沒有依賴性的話,完全可以使用 Promise.all 的方式    // 如果有依賴性的話,其實就是解決回調地獄的例子了    await fetch('XXX1')    await fetch('XXX2')    await fetch('XXX3')  }

下面來看一個使用 await 的例子:

let a = 0  let b = async () => {    a = a + await 10    console.log('2', a) // -> '2' 10  }  b()  a++  console.log('1', a) // -> '1' 1

對於以上程式碼你可能會有疑惑,讓我來解釋下原因

  • 首先函數 b 先執行,在執行到 await 10 之前變數 a 還是 0,因為 await 內部實現了 generatorgenerator 會保留堆棧中東西,所以這時候 a = 0 被保存了下來
  • 因為 await 是非同步操作,後來的表達式不返回 Promise 的話,就會包裝成 Promise.reslove(返回值),然後會去執行函數外的同步程式碼
  • 同步程式碼執行完畢後開始執行非同步程式碼,將保存下來的值拿出來使用,這時候 a = 0 + 10

上述解釋中提到了 await 內部實現了 generator,其實 await 就是 generator 加上 Promise的語法糖,且內部實現了自動執行 generator。如果你熟悉 co 的話,其實自己就可以實現這樣的語法糖。