Right now there are two main ways to handle async control flow in JS. We’ll take a short look on them. All functions are written as async for demonstration reasons.

Callbacks

Callbacks are the main way to handle async operations since very early Node.js versions. They follow the pattern cb($error, $data)

Callback Hell

This way of nesting function that depend on each other is often referenced as “callback hell”:

function start ()
  network.get('http://example.com/', function (err, data) {
    if (err) return cb(err)
    processData(data, function (er, data) {
      render(data, function (err2, data) {
         // deep nesting
      })
    })
  })
}

 

 

Example

function getWebsiteData (cb) {
  network.get('http://example.com/', function (err, data) {
    if (err) return cb(err)
    
    cb(null, data)
  })
}

function processData (data, cb) {  
  const result = data + ' appended text'
  cb(null, result) 
}

function render (data, cb) {
  // renders final result
  // ... yada yada
  cb(null)
}

// put together
getWebsiteData(function (err, data) {
  if (err) // handle error
  
  processData(null, data, render)
})

Advanced Callback Handling

The async module has very good support for advanced callback handling and their scheduling.

Conclusion

Cons: Flow of the program / Error handling can be confusing to read.
Pro: low level

Tip: use named functions to avoid callback hell / deep callback nesting

Promises

Promises are similar to to Futures in Java and C#. They can be used to wrap callback APIs. They are part of the ES6 spec and Node has native Promise support since v4.0.0.

Example

function getWebsiteData () {
  return new Promise(function (resolve, reject) {
    network.get('http://example.com/', function (err, data) {
      if (err) return reject(err)
      // a sync `throw new Error` also works as expected! :)
    
      resolve(data)
    })
  })
}

function processData (data) {
  return new Promise(function (resolve, reject) {
    const result = data + ' appended text'
    resolve(result)  
  })
}

function render (data) {
  return new Promise(function (resolve, reject) {
    // renders final result
    // ... yada yada
  
    resolve()
  })
}

// put together

getWebsiteData()
  .then(function (result) {
    // do some operations
    return processData(result)
  })
  .then(render)
  .catch(function (err) {
    // handle errors
  })

Advanced Control Flows with Promises

For queuing multiple Promises the module bluebird is a great choice. However, there are some nice functions available out of the box.

Multiple async operations

Let’s say we have a few Network requests and want to continue once all are finished. jQuery supports a Deferreds/Promises for Network calls.

const multipleRequests = []

multipleRequests
  .push($.get('http://example.com'))
  .push($.get('http://example.org'))

Promise.all(multipleRequests)
  .then(function (response1, response2) {

  })



Conclusion

Pro: easier to avoid callback hell, error handling easier to reason about

Cons: a new API to learn

The Future: async / await

ES8 will probably come with async/await support in 2018. It will take some time until it is implemented by all major platforms.

The draft is located at https://tc39.github.io/ecmascript-asyncawait and it can still change.

References

https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Global_Objects/Promise/all
http://api.jquery.com/jQuery.ajax/#jqXHR
http://bluebirdjs.com/docs/getting-started.html
http://caolan.github.io/async/docs.html#controlflow

 

 

 

 

 

 

  • No labels