JavaScript異步處理的那些事兒

前言

原文git

以前總結了關於 JavaScript 異步的 事件循環與消息隊列 機制以及 ES6 帶來的 微任務與宏任務 的知識。傳送門github

下面是關於JS異步處理的各類方案:ajax

callback >> ES6 Primise >> async/await
複製代碼

沒有異步處理

先看一段代碼:bash

// 假設有一個耗時的異步請求 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()async

異步的解決方案

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 中去,並在異步請求結束後執行回調函數裏的操做。post

問題彷佛已經解決了??可是,若是有多個異步請求,而且在每一個異步請求完成後都執行一些操做,那代碼就會像下面這樣:學習

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,而後還有請求三、請求四、請求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,只能說。。。

技術發展太快啦,趕忙學習吧!

相關文章
相關標籤/搜索