JavaScript 異步編程的四種方式

異步編程是每一個使用 JavaScript 編程的人都會遇到的問題,不管是前端的 ajax 請求,或是 node 的各類異步 API。本文就來總結一下常見的四種處理異步編程的方法。javascript

回調函數

使用回調函數是最多見的一種形式,下面來舉幾個例子:html

// jQuery ajax
$.get('test.html', data => {
  $('#result').html(data)
})
// node 異步讀取文件
const fs = require('fs')

fs.readFile('/etc/passwd', (err, data) => {
  if (err) {
    throw err
  }
  console.log(data)
})

回調函數很是容易理解,就是定義函數的時候將另外一個函數(回調函數)做爲參數傳入定義的函數當中,當異步操做執行完畢後在執行該回調函數,從而能夠確保接下來的操做在異步操做以後執行。前端

回調函數的缺點在於當須要執行多個異步操做的時候會將多個回調函數嵌套在一塊兒,組成代碼結構上混亂,被稱爲回調地獄(callback hell)。java

func1(data0, data1 => {
  func2(data2, data3 => {
    func3(data3, data4 => data4)
  })
})

Promise

Promise 利用一種鏈式調用的方法來組織異步代碼,能夠將原來以回調函數形式調用的代碼改成鏈式調用。node

// jQuery ajax promise 方式
$.get('test.html')
  .then(data => $(data))
  .then($data => $data.find('#link').val('href'))
  .then(href => console.log(href))

本身定義一個 Promise 形式的函數在 ES6 當中也很是簡單:git

function ready() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve('ready')
    }, 3000)
  })
}

ready().then(ready => console.log(`${ready} go!`))

在 node 8.0 以上的版本還能夠利用 util.promisify 方法將回調形式的函數變爲 Promise 形式。es6

const util = require('util')
const fs = require('fs')

const readPromise = util.promisify(fs.readFile)

readPromise('test.txt').then(data => console.log(data.toString()))

想詳細瞭解 Promise 能夠閱讀拙做談談 ES6 的 Promise 對象github

Generators

node 的著名開發者 TJ 利用 ES6 新特性生成器(Generators)開發了一個異步控制工具 coajax

若是不瞭解 Generators 能夠看看如下的文章:編程

利用 co 能夠將異步代碼的寫法寫成相似同步代碼的形式:

const util = require('util')
const fs = require('fs')
const co = require('co')

const readFile = util.promisify(fs.readFile)

co(function* () {
  const txt = yield readFile('file1.txt', 'utf8')
  console.log(txt)
  const txt2 = yield readFile('file2.txt', 'utf8')
  console.log(txt2)
})

使用 Generators 的好似顯然易見,可使異步代碼寫得很是清晰,缺點就是要另外引入相關的庫來利用該特性。

Async/Await

node7.6 以上的版本引入了一個 ES7 的新特性 Async/Await 是專門用於控制異步代碼。先看一個例子:

const util = require('util')
const fs = require('fs')

const readFile = util.promisify(fs.readFile)

async function readFiles () {
  const txt = await readFile('file1.txt', 'utf8')
  console.log(txt)
  const txt2 = await readFile('file2.txt', 'utf8')
  console.log(txt2)
})

首先要使用 async 關鍵字定義一個包含異步代碼的函數,在 Promise 形式的異步函數前面使用 await 關鍵字就能夠將異步寫成同步操做的形式。

看上去與 Generators 控制方式相差不大,可是 Async/Await 是原生用於控制異步,因此是比較推薦使用的。

錯誤處理

最後來探討下四種異步控制方法的錯誤處理。

回調函數

回調函數錯誤處理很是簡單,就是在回調函數中同時回傳錯誤信息:

const fs = require('fs')

fs.readFile('file.txt', (err, data) => {
  if (err) {
    throw err
  }
  console.log(data)
})

Promise

Promise 在 then 方法以後使用一個 catch 方案來捕捉錯誤信息:

const fs = require('fs')
const readFile = util.promisify(fs.readFile)

readFile('file.txt')
  .then(data => console.log(data))
  .catch(err => console.log(err))

Generators 和 Async/Await

Generators 和 Async/Await 比較相似,能夠有兩種方式,第一種使用 Promise 的 catch 方法,第二種用 try catch 關鍵字。

Promise catch

const fs = require('fs')
const co = require('co')
const readFile = util.promisify(fs.readFile)

co(function* () {
  const data = yield readFile('file.txt').catch(err => console.log(err))
})
const fs = require('fs')
const co = require('co')
const readFile = util.promisify(fs.readFile)

async function testRead() {
  const data = await readFile('file.txt').catch(err => console.log(err))
}

try/catch

const fs = require('fs')
const co = require('co')
const readFile = util.promisify(fs.readFile)

co(function* () {
  try {
    const data = yield readFile('file.txt')
  } catch(err) {
    console.log(err)
  }
})
const fs = require('fs')
const readFile = util.promisify(fs.readFile)

async function testRead() {
  try {
    const data = await readFile('file.txt')
  } catch(err) {
    console.log(data)
  }
}

感謝您的閱讀,有不足之處請爲我指出。

參考

  1. 談談 ES6 的 Promise 對象

  2. 深刻淺出ES6(三):生成器 Generators

  3. 深刻淺出ES6(十一):生成器 Generators,續篇

本文同步於個人我的博客 http://blog.acwong.org/2017/06/24/javascript-async-programming/

相關文章
相關標籤/搜索