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