Use await and defer to execute asynchronous functions synchronously.

Synchronize assumes node-style (err, result) callbacks. The second parameter given to the cb will be returned. If an error is present it will be thrown and can be intercepted by a try/catch statement. Multiple and named arguments are supported with defers.

You can use the helper sync(fn) and it will use await/defer automatically. The helper is non-destructive and the function can still be called asynchronously if called with a callback. You can also mix it with other synchronous or asynchronous code.

Use await/defer manually to get precise control or automatically to write compact and clean code.

Install it with npm install synchronize.

The project is hosted on GitHub.
You can report bugs and discuss features on the Issues page.

Usage patterns

data = sync.await(fs.readFile(fname, sync.defer()))

Use await and defer keywords by hand to call a function synchronously (you can make await and defer global to make things shorter).

sync(fs, 'readFile')

var data = fs.readFile(fname)
fs.readFile(fname, function(err, data)){}

Use sync() to make an async function synchronize.js-aware. You can use it in both ways - synchronous or asynchronous. Basically it uses the same await/defer pattern, but automatically. This object form preserves context, unlike the form below.

asyncFn = sync(asyncFn)

var data = asyncFn(input)
// You can still use the async form of this fn
var data = sync.await(asyncFn(input, sync.defer()))
asyncFn(input, function(err, data){})

You can use sync() on a bare function to return a synchronized version of the function. It can still be used in asynchronous form.

Basic usage, printing content of file

var sync = require('synchronize')
var fs   = require('fs')

sync(fs, 'readFile')

sync.fiber(function(){
  var data = fs.readFile(__filename, 'utf8')
  console.log(data)

  try {
    data = 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 a Fiber using sync.fiber().

Inside of Fiber we can call asynchronous functions as if it's synchronous.

We can also use standard try/catch statements to catch asynchronous errors.

Or call readFile asynchronously if we wish.

Listing and printing files in directory

Listing content of current directory, checking if path is file and printing its content to console.

Using synchronize

var sync = require('synchronize')
var fs   = require('fs')

sync(fs, 'readdir', 'stat', 'readFile')

sync.fiber(function(){
  var i, paths, path, stat, data
  paths = fs.readdir('.')
  for(i = 0; i < paths.length; i++){
    path = paths[i]
    stat = fs.stat(path)
    if(!stat.isFile()) continue
    data = 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)
})

Parallel and serial operations

Synchronize.js can run multiple operations in parallel using sync.parallel().

Async operations in serial

var sync = require('synchronize')

var read = function(a, cb) {
  setTimeout(function(){
    // remember that callbacks expect (err, result)
    cb(null, a)
  }, 1000)
}

// We can use the `sync` helper here to avoid using
// `await()` and `defer()` manually.
read = sync(read);

sync.fiber(function() {
  var results = []
  results.push(read(1)))
  results.push(read(2)))
  // results now contains [1,2]
})

Async operations in parallel

var sync = require('synchronize')

function read(a, cb) {
  setTimeout(function(){
    cb(null, a)
  }, 1000)
}

// Runs in parallel
sync.fiber(function() {
  sync.parallel(function() {
    // You must call defer() manually within
    // a parallel operation.
    read(1, sync.defer())
    read(2, sync.defer())
  });
  var results = sync.await()
  // results now contains [1,2]
});

Multiple arguments in callbacks

Synchronize.js accepts multiple arguments in callbacks using sync.defers().

Multiple arguments in serial

var sync = require('synchronize')

function read(a, b, c, cb) {
  setTimeout(function(){
    cb(null, a, b, c)
  }, 1000)
}

sync.fiber(function() {
  // Returns [1,2,3]
  var results = sync.await(
    read(1, 2, 3, sync.defers()))
  // Returns {a: 1, b: 2, c: 3}
  var namedResults = sync.await(
    read(4, 5, 6, sync.defers('a', 'b', 'c')))
})

Multiple arguments in parallel

var sync = require('synchronize')

function read(a, b, c, cb) {
  setTimeout(function(){
    cb(null, a, b, c)
  }, 1000)
}

sync.fiber(function() {
  sync.parallel(function(){
    read(1, 2, 3, sync.defers())
    read(4, 5, 6, sync.defers())
  });
  // returns [[1,2,3],[4,5,6]]
  var results = sync.await()
  // concat to get [1,2,3,4,5,6]
  results = [].concat.apply([], results)
})

Usage with Express.js

var sync    = require('synchronize')
var fs      = require('fs')
var express = require('express')

sync(fs, 'readFile')

var app = express.createServer()
app.use(function(req, res, next){
  sync.fiber(next)
})

app.get('/', function(req, res){
  var data = 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 is one possible way to use it with Express.js.

Usage with Mocha.js

var sync  = require('synchronize')
var fs    = require('fs')

sync(fs, 'readFile')

var async = sync.asyncIt

describe('File System', function(){
  it('should read file', async(function(){
    var data = fs.readFile(__filename, 'utf8')
  }))
})

You can use the sync.asyncIt helper to greatly simplify writing asynchronous tests.

You may also take a look at this real-life test scenario that uses synchronize to simplify asynchronous calls for MongoDB.

Similar tools

MonoJS - synchronous web framework based on synchronize.


Copyright Alexey Petrushin, released under MIT License

Fork me on GitHub