Eirik Langholm Vullum PRO
JavaScript fanatic that loves node.js and React.
https://slides.com/eiriklv/taming-async-talk
https://github.com/eiriklv/taming-async-talk
twitter @eiriklv
Asynchronous JavaScript can
step(result, function(err, result) {
step(result, function(err, result) {
step(result, function(err, result) {
step(result, function(err, result) {
step(result, function(err, result) {
step(result, function(err, result) {
step(result, function(err, result) {
step(result, function(err, result) {
step(result, function(err, result) {
step(result, function(err, result) {
step(result, function(err, result) {
step(result, function(err, result) {
step(result, function(err, result) {
step(result, function(err, result) {
console.log('woopwoop!');
})
})
})
})
})
})
})
})
})
})
})
})
})
})
We have a contrived express.js route that:
app.get('/', function(req, res) {
let inputFile = 'input.txt';
try {
let inputData = fs.readFileSync(inputFile);
let processedData1 = process1Sync(inputData);
let processedData2 = process2Sync(processedData1);
let result = process3Sync(processedData2);
res.status(200).send(result);
} catch (err) {
res.status(500).send(err);
}
});
function process1Sync(input) {
var start = Date.now();
var len = 100;
while (Date.now() < (start + len)) {}
return input + ' - processed';
};
(nesting callbacks)
app.get('/', function(req, res) {
let inputFile = 'input.txt';
fs.readFile(inputFile, function(err, inputData) {
if (err) return res.status(500).send(err);
process1(inputData, function(err, processedData1) {
if (err) return res.status(500).send(err);
process2(processedData1, function(err, processedData2) {
if (err) return res.status(500).send(err);
process3(processedData2, function(err, result) {
if (err) return res.status(500).send(err);
res.status(200).send(result);
});
});
});
});
});
function process1(input, callback) {
let output = input + ' - processed';
setTimeout(callback.bind(null, null, input), 100);
};
(separating each step of the flow)
app.get('/', function(req, res) {
let inputFile = 'input.txt';
fs.readFile(inputFile, onReadFile);
function onReadFile(err, inputData) {
if (err) return res.status(500).send(err);
process1(inputData, onProcess1);
}
function onProcess1(err, processedData1) {
if (err) return res.status(500).send(err);
process2(processedData1, onProcess2);
}
function onProcess2(err, processedData2) {
if (err) return res.status(500).send(err);
process3(processedData2, onDone);
}
function onDone(err, result) {
if (err) return res.status(500).send(err);
res.status(200).send(result);
}
});
app.get('/', function(req, res) {
let inputFile = 'input.txt';
async.waterfall([
function(callback) {
fs.readFile(inputFile, function(err, inputData) {
callback(err, inputData);
});
},
function(inputData, callback) {
process1(inputData, function(err, processedData1) {
callback(err, processedData1);
});
},
function(processedData1, callback) {
process2(processedData1, function(err, processedData2) {
callback(err, processedData2);
});
},
function(processedData2, callback) {
process3(processedData2, function(err, processedData3) {
callback(err, processedData3);
});
}
], function(err, result) {
if (err) return res.status(500).send(err);
res.status(200).send(result);
});
});
app.get('/', function(req, res) {
let inputFile = 'input.txt';
async.waterfall([
fs.readFile.bind(fs, inputFile),
process1,
process2,
process3
], function(err, result) {
if (err) return res.status(500).send(err);
res.status(200).send(result);
});
});
app.get('/', function(req, res) {
let inputFile = 'input.txt';
let flow = [
fs.readFile.bind(fs, inputFile),
process1,
process2,
process3
];
let done = function(err, result) {
if (err) return res.status(500).send(err);
res.status(200).send(result);
};
async.waterfall(flow, done);
});
app.get('/', function(req, res) {
let inputFile = 'input.txt';
let flow = async.compose(
process3,
process2,
process1,
fs.readFile.bind(fs, inputFile)
);
let done = function(err, result) {
if (err) return res.status(500).send(err);
res.status(200).send(result);
};
flow(done);
});
app.get('/', function(req, res) {
let inputFile = 'input.txt';
promisify(fs.readFile.bind(fs))(inputFile)
.then(promisify(process1))
.then(promisify(process2))
.then(promisify(process3))
.then(function(result) {
res.status(200).send(result);
})
.catch(function(err) {
res.status(500).send(err);
});
});
function promisify(fn) {
return function() {
let self = this;
let args = Array.prototype.slice.call(arguments);
return new Promise(function(resolve, reject) {
let callback = function(err, result) {
if (err) return reject(err);
resolve(result);
};
fn.apply(self, args.concat(callback));
});
};
}
app.get('/', function(req, res) {
let inputFile = 'input.txt';
fs.readFileAsync(inputFile)
.then(Promise.promisify(process1))
.then(Promise.promisify(process2))
.then(Promise.promisify(process3))
.then(function(result) {
res.status(200).send(result);
})
.catch(function(err) {
res.status(500).send(err);
});
});
const fs = Promise.promisifyAll(require('fs'));
// fs.readFile -> fs.readFileAsync
// fs.writeFile -> fs.writeFileAsync
// fs.appendFile -> fs.appendFileAsync
// ...
app.get('/', function(req, res) {
let inputFile = 'input.txt';
let data = hl([inputFile]);
data
.flatMap(hl.wrapCallback(fs.readFile.bind(fs)))
.flatMap(hl.wrapCallback(process1))
.flatMap(hl.wrapCallback(process2))
.flatMap(hl.wrapCallback(process3))
.stopOnError(function(err) {
res.status(500).send(err);
})
.apply(function(result) {
res.status(200).send(result);
});
});
data
.flatMap(hl.wrapCallback(
async.compose(
process3,
process2,
process1,
fs.readFile.bind(fs)
)
))
.stopOnError(handleError)
.apply(handleSuccess);
function *fooGen() {
var x = yield 'bar';
var y = yield 'baz';
console.log('result: ', x + y);
}
var fooGenIterator = fooGen();
fooGenIterator.next(); // { value: 'bar', done: false }
fooGenIterator.next(10); // { value: 'baz', done: false }
fooGenIterator.next(10); // { value: undefined, done: true }
> result: 23
app.get('/', function(req, res) {
run(function *() {
let inputFile = 'input.txt';
try {
let inputData = yield fs.readFile.bind(fs, inputFile);
let processedData1 = yield process1.bind(null, inputData);
let processedData2 = yield process2.bind(null, processedData1);
let result = yield process3.bind(null, processedData2);
res.status(200).send(result);
} catch (err) {
res.status(500).send(err);
}
});
});
function run(fn) {
let gen = fn();
function next(err, res) {
if (err) return gen.throw(err);
let ret = gen.next(res);
if (ret.done) return;
ret.value(next);
}
next();
};
app.get('/', function(req, res) {
run(function *() {
let inputFile = 'input.txt';
try {
let inputData = yield curry(fs.readFile.bind(fs))(inputFile);
let processedData1 = yield curry(process1)(inputData);
let processedData2 = yield curry(process2)(processedData1);
let result = yield curry(process3)(processedData2);
res.status(200).send(result);
} catch (err) {
res.status(500).send(err);
}
});
});
function curry(fn) {
let args = Array.prototype.slice.call(arguments, 1);
return (function curryFn(prevArgs) {
return function(arg) {
let totalArgs = prevArgs.concat(Array.prototype.slice.call(arguments));
if (!arg) totalArgs.push(undefined);
if (totalArgs.length < fn.length) {
return curryFn(totalArgs);
} else {
return fn.apply(null, totalArgs);
}
};
}(args));
};
app.get('/', function(req, res) {
run(function *() {
let inputFile = 'input.txt';
try {
let inputData = yield promisify(fs.readFile.bind(fs))(inputFile);
let processedData1 = yield promisify(process1)(inputData);
let processedData2 = yield promisify(process2)(processedData1);
let result = yield promisify(process3)(processedData2);
res.status(200).send(result);
} catch (err) {
res.status(500).send(err);
}
});
});
function run(fn) {
let gen = fn();
function next(err, res) {
if (err) return gen.throw(err);
let 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();
}
const co = require('co');
app.get('/', function(req, res) {
co(function *() {
let inputFile = 'input.txt';
try {
let inputData = yield promisify(fs.readFile.bind(fs))(inputFile);
let processedData1 = yield promisify(process1)(inputData);
let processedData2 = yield promisify(process2)(processedData1);
let result = yield promisify(process3)(processedData2);
res.status(200).send(result);
} catch (err) {
res.status(500).send(err);
}
}).catch(function(err) {...});
});
const readFile = promisify(fs.readFile.bind(fs));
const process1 = promisify(require('./process1'));
const process2 = promisify(require('./process2'));
const process3 = promisify(require('./process3'));
app.get('/', function(req, res) {
co(function *() {
let inputFile = 'input.txt';
try {
let inputData = yield readFile(inputFile);
let processedData1 = yield process1(inputData);
let processedData2 = yield process2(processedData1);
let result = yield process3(processedData2);
res.status(200).send(result);
} catch (err) {
res.status(500).send(err);
}
}).catch(function(err) {...});
});
// shim native modules with promises
const fs = require('mz/fs');
// use .promisify(..) / .promisifyAll(..)
// to shim user-libs
const Promise = require('bluebird');
app.get('/', function(req, res) {
co(function *() {
let inputFile = 'input.txt';
try {
let inputData = yield fs.readFile(inputFile);
let processedData1 = yield process1(inputData);
let processedData2 = yield process2(processedData1);
let result = yield process3(processedData2);
res.status(200).send(result);
} catch (err) {
res.status(500).send(err);
}
}).catch(function(err) {...});
});
app.post('/process-file', 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 processedData3 = process3.sync(processedData2);
fs.sync.writeFile(outputFile, processedData3);
res.status(200).send('success');
} catch (err) {
res.status(500).send(err);
}
}, function(err) {...});
});
ES7 async await
http://jakearchibald.com/2014/es7-async-functions/
CSP (Communicating Sequencial Processes)
https://github.com/ubolonton/js-csp
https://www.youtube.com/watch?v=W2DgDNQZOwo
Generators (and io.js)
http://davidwalsh.name/es6-generators
By Eirik Langholm Vullum
Talk for Node.js Oslo Meetup