Four Ways of JavaScript Asynchronous Programming

Keywords: Javascript Programming JQuery

Asynchronous programming is a problem that every JavaScript programmer will encounter, whether it's a front-end ajax request or a node's various asynchronous API s. This article will summarize four common methods to deal with asynchronous programming.

callback

The use of callback functions is the most common form. Here are a few examples:

// jQuery ajax
$.get('test.html', data => {
  $('#result').html(data)
})
// node reads files asynchronously
const fs = require('fs')

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

Callback function is very easy to understand, that is, when defining a function, another function (callback function) is passed into the defined function as a parameter. When the asynchronous operation is completed, the callback function is executed, so as to ensure that the next operation is executed after the asynchronous operation.

The disadvantage of callback function is that when many asynchronous operations need to be performed, many callback functions will be nested together and the code structure is chaotic, which is called callback hell.

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

Promise

Promise uses a chain call method to organize asynchronous code, which can change the code originally called in the form of callback function to chain call.

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

Defining a function in Promise form is also very simple in ES6.

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

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

In versions above node 8.0, util.promisify method can also be used to convert callback functions into Promise forms.

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

const readPromise = util.promisify(fs.readFile)

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

To get a better understanding of Promise, you can read my poems. On Promise Object of ES6.

Generators

TJ, the famous developer of node, developed an asynchronous control tool using ES6 Generators. co.

If you don't know Generators, you can read the following articles:

Using co, asynchronous code can be written in a form similar to synchronous code:

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)
})

It seems obvious that using Generators can make asynchronous code very clear, but the disadvantage is to introduce additional libraries to take advantage of this feature.

Async/Await

Versions above node7.6 introduce a new feature of ES7, Async/Await, which is specifically designed to control asynchronous code. Let's start with an example:

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)
})

First, we need to use the async keyword to define a function that contains asynchronous code. We can write asynchronous into synchronous operation by using await keyword before the asynchronous function in Promise form.

It seems to be similar to Generators control, but Async/Await is native to control asynchrony, so it is recommended.

error handling

Finally, the error handling of the following four asynchronous control methods is discussed.

callback

The error handling of callback function is very simple. It is to return error information in callback function at the same time.

const fs = require('fs')

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

Promise

Promise uses a catch scheme after the then method to capture error messages:

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

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

Generators and sync/Await

Generators are similar to Async/Await in two ways: the first uses Promise's catch method and the second uses try catch keyword.

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)
  }
}

Thank you for your reading. Please point out the shortcomings for me.

Reference resources

  1. On Promise Object of ES6

  2. ES6 (3): Generators

  3. ES6 (11): Generators, sequel

Posted by moty66 on Tue, 18 Jun 2019 10:48:50 -0700