JavaScript異步發展歷史

前言

在 MDN 的 JavaScript 系列中咱們已經學習了 callback、promise、generator、async/await。而在這一篇文章中,做者將以實際樣例闡述異步發展歷史,介紹每種實現方式的優點與不足,以期幫助讀者熟悉歷史進程並把握異步發展的脈絡。 異步發展歷史javascript

異步

幾十年前的導航網站,清爽又簡單,沒有什麼特別的功能,只是單純的展現,現成的網頁在服務器上靜靜躺着,高效毫無壓力,讓人很喜歡。html

幾十年後的今天,靜態頁面遠不能知足用戶的需求,網站變得複雜起來,用戶交互愈來愈頻繁,從而產生大量複雜的內部交互,爲了解決這種複雜,出現了各類系統「模式」,從而很容易的在外部獲取數據,並實時展現給用戶。java

獲取外部數據實際上就是「網絡調用」,這個時候「異步」這個詞彙出現了。編程

異步指兩個或兩個以上的對象或事件不一樣時存在或發生(或多個相關事物的發生無需等待其前一事物的完成)

image-20210116210456133

異步 callbacks

異步 callbacks其實就是函數,只不過是做爲參數傳遞給那些在後臺執行的其餘函數。當那些後臺運行的代碼結束,就調用 callbacks 函數,通知你工做已經完成,或者其餘有趣的事情發生了。

場景segmentfault

let readFile = (path, callBack) => {
  setTimeout(function () {
    callBack(path)
  }, 1000)
}

readFile('first', function () {
  console.log('first readFile success')
  readFile('second', function () {
    console.log('second readFile success')
    readFile('third', function () {
      console.log('third readFile success')
      readFile('fourth', function () {
        console.log('fourth readFile success')
        readFile('fifth', function () {
          console.log('fifth readFile success')
        })
      })
    })
  })
})

優勢:promise

  • 解決了同步問題(即解決了一個任務時間長時,後面的任務排隊,耗時過久,使程序的執行變慢問題)

缺點:服務器

  • 回調地獄(多層級嵌套),會致使邏輯混亂,耦合性高,改動一處就會致使所有變更,嵌套多時,錯誤處理複雜
  • 不能使用 try...catch 來抓取錯誤
  • 不能 return
  • 可讀性差

Promise

一個 Promise對象表明一個在這個 promise 被建立出來時不必定已知的值。它讓您可以把異步操做最終的成功返回值或者失敗緣由和相應的處理程序關聯起來。 這樣使得異步方法能夠像同步方法那樣返回值:異步方法並不會當即返回最終的值,而是會返回一個 promise,以便在將來某個時候把值交給使用者。

場景網絡

let readFile = (path) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (!path) {
        reject('error!!!')
      } else {
        console.log(path + ' readFile success')
        resolve()
      }
    }, 1000)
  })
}

readFile('first')
  .then(() => readFile('second'))
  .then(() => readFile('third'))
  .then(() => readFile('fourth'))
  .then(() => readFile('fifth'))

優勢:異步

  • 狀態改變後,就不會再變,任什麼時候候均可以獲得這個結果
  • 能夠將異步操做以同步操做的流程表達出來,避免了層層嵌套的回調函數
  • 必定程度上解決了回調地獄的可讀性問題

缺點:async

  • 沒法取消 promise
  • 當處於 pending 狀態時,沒法得知目前進展到哪個階段
  • 代碼冗餘,有一堆任務時也會使語義不清晰

Generator

Generator函數是 ES6 中提供的一種 異步編程解決方案。語法上,首先能夠把它理解成, Generator函數是一個 狀態機,封裝了多個內部狀態,須要使用 next()函數來繼續執行下面的代碼。

特徵

  • function 與函數名之間帶有(*)
  • 函數體內部使用 yield 表達式,函數執行遇到 yield 就返回

場景

var readFile = function (name, ms) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log(name + '讀完了')
      resolve()
    }, ms)
  })
}

var gen = function* () {
  console.log('指定generator')
  yield readFile('first', 1000)
  yield readFile('second', 2000)
  yield readFile('third', 3000)
  yield readFile('forth', 4000)
  yield readFile('fifth', 5000)
  return '完成了'
}
var g = gen()
var result = g.next()
result.value
  .then(() => {
    g.next()
  })
  .then(() => {
    g.next()
  })
  .then(() => {
    g.next()
  })
  .then(() => {
    g.next()
  })

優勢:

  • 能夠控制函數的執行,能夠配合 co 函數庫使用

缺點:

  • 流程管理卻不方便(即什麼時候執行第一階段、什麼時候執行第二階段)

async 和 await

async functionsawait 關鍵字是最近添加到 JavaScript 語言裏面的。它們是 ECMAScript 2017 JavaScript 版的一部分(參見 ECMAScript Next support in Mozilla)。簡單來講,它們是基於 promises 的語法糖,使異步代碼更易於編寫和閱讀。經過使用它們,異步代碼看起來更像是老式同步代碼,所以它們很是值得學習。

image-20210116211018846

場景 1

var readFile = function (name, ms) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log(name + '讀完了')
      resolve()
    }, ms)
  })
}

async function useAsyncAwait() {
  await readFile('first', 1000)
  await readFile('second', 2000)
  await readFile('third', 3000)
  await readFile('forth', 4000)
  await readFile('fifth', 5000)
  console.log('async文件閱讀完畢')
}
useAsyncAwait()

優勢

  • 內置執行器。意味着不須要像 generator 同樣調用 next 函數或 co 模塊
  • 更廣的適用性。async 和 await 後面跟的都是 promise 函數,原始數據類型會被轉爲 promise
  • 語義更清晰、簡潔

    缺點

  • 大量的 await 代碼會阻塞(程序並不會等在原地,而是繼續事件循環,等到響應後繼續往下走)程序運行,每一個 await 都會等待前一個完成

場景 2 場景 1 中的代碼,其實 second,third 的僞請求其實並不依賴於 first,second 的結果,但它們必須等待前一個的完成才能繼續,而咱們想要的是它們同時進行,因此正確的操做應該是這樣的。

async function useAsyncAwait() {
  const first = readFile('first', 1000)
  const second = readFile('second', 2000)
  const third = readFile('third', 3000)
  const forth = readFile('forth', 4000)
  const fifth = readFile('fifth', 5000)
  console.log('async文件閱讀完畢')

  await first
  await second
  await third
  await forth
  await fifth
}
useAsyncAwait()

在這裏,咱們將三個 promise 對象存儲在變量中,這樣能夠同時啓動它們關聯的進程。

總結

在這篇文章中,咱們已經介紹了 JavaScript 異步發展史中 --- callback、promise、generator、async/await 的使用方式、優勢與缺點。

發展史 優勢 缺點
callback 解決了同步問題 回調地獄、可讀性差、沒法 try / catch 、沒法 return
promise 必定程度上解決了回調地獄的可讀性 沒法取消、任務多時,一樣存在語義不清晰
generator 能夠控制函數的執行,能夠配合 co 函數庫使用 流程管理卻不方便(即什麼時候執行第一階段、什麼時候執行第二階段
async/await 語義更清晰、簡潔,內置執行器 認知不清晰可能會形成大量 await 阻塞(程序並不會等在原地,而是繼續事件循環,等到響應後繼續往下走)狀況

而在現有的異步解決方案中,async/await 是使用人數最多的,它帶給咱們最大的好處即同步代碼的風格,語義簡潔、清晰。

相關文章