This post is the continuation of a previous post about asynchronous flow in JavaScript/node.js

This time we'll look at

fibers (fibrous.js)

generators (ES6)

generators + co + mz

We'll still use my (poor) example of an express route:

reading from a file

doing some processing (in 3 steps)

process* is just some arbitrary async operation that calls back with extended data

writing the result to a file

responding to the request with either a success or error message

Approach 1 - Using fibers

var fs = require('fs'); var express = require('express'); var fibrous = require('fibrous'); var app = express(); app.get('/', function(req, res) { fibrous.run(function() { var inputFile = 'input.txt'; var outputFile = 'output.txt'; try { var inputData = fs.sync.readFile(inputFile); var processedData1 = process1.sync(inputData); var processedData2 = process2.sync(processedData1); var result = process3.sync(processedData2); fs.sync.writeFile(outputFile, result); res.status(200).send('success'); } catch (err) { res.status(500).send(err); } }, function(err) {...}); });

Using fibers out flow looks a lot like synchronous code, since our async functions now return a value. This is some of the "magic" of fibers. All functions/object-methods now have a .sync version that enables this.

Approach 2 - Generators (ES6)

Generators is a new construct introduced in ES6, giving you the ability to create generator functions using the function *() keyword. These generator functions returns an iterator when called ( var iter = genFun(); ) which has the special ability of letting you step through your code until you hit the newly introduced yield keyword, which will pause the execution while waiting for a return value, without blocking the execution of your application.

Read more about them here

example using partial application

var fs = require('fs'); var express = require('express'); var run = require('../../lib/runner'); var app = express(); app.get('/', function(req, res) { run(function *() { var inputFile = 'input.txt'; var outputFile = 'output.txt'; try { var inputData = yield fs.readFile.bind(fs, inputFile); var processedData1 = yield process1.bind(null, inputData); var processedData2 = yield process2.bind(null, processedData1); var result = yield process3.bind(null, processedData2); yield fs.writeFile.bind(fs, outputFile, result); res.status(200).send('success'); } catch (err) { res.status(500).send(err); } }); });

where the iterator runner function run is:

function run(fn) { var gen = fn(); function next(err, res) { if (err) return gen.throw(err); var ret = gen.next(res); if (ret.done) return; ret.value(next); } next(); };

example using currying (from lodash)

var fs = require('fs'); var express = require('express'); var run = require('../../lib/runner'); var _ = require('lodash'); var app = express(); app.get('/', function(req, res) { run(function *() { var inputFile = 'input.txt'; var outputFile = 'output.txt'; try { var inputData = yield _.curry(fs.readFile.bind(fs))(inputFile); var processedData1 = yield _.curry(process1)(inputData); var processedData2 = yield _.curry(process2)(processedData1); var result = yield _.curry(process3)(processedData2); yield _.curry(fs.writeFile.bind(fs))(outputFile, result); res.status(200).send('success'); } catch (err) { res.status(500).send(err); } }); });

example using thunks

var fs = require('fs'); var express = require('express'); var run = require('../../lib/runner'); var thunkify = require('../../lib/thunkify'); var app = express(); app.get('/', function(req, res) { run(function *() { var inputFile = 'input.txt'; var outputFile = 'output.txt'; try { var inputData = yield thunkify(fs.readFile.bind(fs))(inputFile); var processedData1 = yield thunkify(process1)(inputData); var processedData2 = yield thunkify(process2)(processedData1); var result = yield thunkify(process3)(processedData2); yield thunkify(fs.writeFile.bind(fs))(outputFile, result); res.status(200).send('success'); } catch (err) { res.status(500).send(err); } }); });

Where thunkify is a function that basicly does a 2-step curry - meaning you pass input data as the first call, and a callback as the second:

function thunkify(fn) { return function() { var args = Array.prototype.slice.call(arguments, 0, fn.length - 1); return function(done) { fn.apply(null, args.concat(done)); }; }; };

example using promises

var Promise = require('bluebird'); var fs = Promise.promisifyAll(require('fs')); var express = require('express'); var run = require('../../lib/runner'); var app = express(); app.get('/', function(req, res) { run(function *() { var inputFile = 'input.txt'; var outputFile = 'output.txt'; try { var inputData = fs.readFileAsync(inputFile); var processedData1 = yield Promise.promisify(process1)(inputData); var processedData2 = yield Promise.promisify(process2)(processedData1); var result = yield Promise.promisify(process3)(processedData2); yield fs.writeFileAsync(outputFile, result); res.status(200).send('success'); } catch (err) { res.status(500).send(err); } }); });

To be able to use Promises when yielding in our generator function we'll need to modify out iterator runner function run to support them:

function run(fn) { var gen = fn(); function next(err, res) { if (err) return gen.throw(err); var ret = gen.next(res); if (ret.done) return; if (typeof ret.value.then === 'function') { try { ret.value.then(function(value) { next(null, value); }, next); } catch (e) { gen.throw(e); } } else { try { ret.value(next); } catch (e) { gen.throw(e); } } } next(); };

Approach 3 - Generators + co + mz

Instead of building out own iterator runner function we can use the module co by T.J Holowaychuck, which support yielding to a number of options (promises, thunks, arrays, objects, etc..) while wrapping the entire execution in a Promise.

example using co

var Promise = require('bluebird'); var fs = Promise.promisifyAll(require('fs')); var express = require('express'); var co = require('co'); var app = express(); app.get('/', function(req, res) { co(function *() { var inputFile = 'input.txt'; var outputFile = 'output.txt'; try { var inputData = fs.readFileAsync(inputFile); var processedData1 = yield Promise.promisify(process1)(inputData); var processedData2 = yield Promise.promisify(process2)(processedData1); var result = yield Promise.promisify(process3)(processedData2); yield fs.writeFileAsync(outputFile, result); res.status(200).send('success'); } catch (err) { res.status(500).send(err); } }).catch(funtion(err) {...}); });

To get example even cleaner we can use a helper library called mz to shim all native async API's to return a Promise.

example using co + mz

var Promise = require('bluebird'); var fs = require('mz/fs'); var express = require('express'); var co = require('co'); var app = express(); app.get('/', function(req, res) { co(function *() { var inputFile = 'input.txt'; var outputFile = 'output.txt'; try { var inputData = yield fs.readFile(inputFile); var processedData1 = yield Promise.promisify(process1)(inputData); var processedData2 = yield Promise.promisify(process2)(processedData1); var result = yield Promise.promisify(process3)(processedData2); yield fs.writeFile(outputFile, result); res.status(200).send('success'); } catch (err) { res.status(500).send(err); } }).catch(onError); });

See part 1 of this series here.