Use flow.sync(fn) to turn asynchronous function into a new one, that can be
used as if it's synchronous. Result of callback will be returned as return value,
in case of error it will be thrown, so You can use try/catch to catch it.
You can also use flow.sync(obj, fname1, fname2, ...) to synchronize methods
of object.
Synchronized functions are backward compatible and can be used as usual asynchronous functions, it's also can be mixed with other synchronous or asynchronous code. So You can introduce it to Your project gradually, by small steps.
The project hosted on
GitHub
You can report bugs and discuss features on the
Issues page
and install it with npm install control-flow command.
Use yield keyword to pause execution (without blocking Node.js) and wait for
asynchronous result.
flow.sync(fs, 'readFile')
var data = yield(fs.readFile(fname))
fs.readFile(fname, function(err, data)){}
var data = yield(fs.readFile(fname, flow.promise()))
Synchronize functions.
After it You can use it in both ways - synchronously or asynchronously (it stays backward compatible with standard asynchronous calls).
Or, You can provide promise by hand.
var flow = require('control-flow')
var fs = require('fs')
flow.sync(fs, 'readFile')
flow.fiber(function(){
var data = yield(fs.readFile(__filename, 'utf8'))
console.log(data)
try {
data = yield(fs.readFile('invalid', 'utf8'))
} catch (err) {
console.log(err)
}
fs.readFile(__filename, 'utf8', function(err, data){
console.log(data)
})
})
In order to use synchronized functions and pause execution without blocking Node.js we
need to wrap execution into Fiber.
Inside of Fiber we can use yield and call asynchronous functions as if it's synchronous.
We can also use standard try/catch statement to catch asynchronous errors.
Or call readFile asynchronously if we wish so.
Listing content of current directory, checking if path is file and printing its content to console.
Using synchronize
var flow = require('control-flow')
var fs = require('fs')
flow.sync(fs, 'readdir', 'stat', 'readFile')
flow.fiber(function(){
var i, paths, path, stat, data
paths = yield(fs.readdir('.'))
for(i = 0; i < paths.length; i++){
path = paths[i]
stat = yield(fs.stat(path))
if(!stat.isFile()) continue
data = yield(fs.readFile(path, 'utf8'))
console.log(data)
}
})
The same code without synchronization
var fs = require('fs')
var printFile = function(paths, i){
if(i >= paths.length) return
var path = paths[i]
fs.stat(path, function(err, stat){
if(err) throw err
if(stat.isFile()){
fs.readFile(path, 'utf8', function(err, data){
if(err) throw err
console.log(data)
printFile(paths, i + 1)
})
} else {
printFile(paths, i + 1)
}
})
}
fs.readdir('.', function(err, paths){
if(err) throw err
printFile(paths, 0)
})
var flow = require('control-flow')
var fs = require('fs')
var express = require('express')
flow.sync(fs, 'readFile')
var app = express.createServer()
app.use(function(req, res, next){
flow.fiber(next)
})
app.get('/', function(req, res){
var data = yield(fs.readFile(__filename, 'utf8'))
res.send(data, {'Content-Type': 'text/plain'})
})
app.listen(3000)
Synchronized code can be mixed with asynchronous code in any combination.
Here are one of possible way to use it with Express.js.
var flow = require('control-flow')
var fs = require('fs')
flow.sync(fs, 'readFile')
flow.it = function(desc, callback){
it(desc, function(done){
flow.fiber(callback.bind(this), done)
})
}
describe('File System', function(){
flow.it('should read file', function(){
var data = yield(fs.readFile(__filename, 'utf8'))
})
})
You can define flow.it helper and use it to define synchronous
specs.
You may also take a look at real-life test scenario that uses synchronize to simplify asynchronous calls for MongoDB.
Fibers are superset of ES6 Generators, so it's possible to design API in the same way as as if Generators would be already available.
I try to keep API as close to ES6 Generators specification as possible. When generators will be finally available in V8 & Node.js (in about 1-2 years) the API will be almost the same.
I believe the only thing that would needs to be changed is - to remove brackets and change
yield(fn) to yield fn.
Copyright Alexey Petrushin, released under MIT License