Async Patterns

Read some JSON

function readJSONSync(filename) {
  return JSON.parse(fs.readFileSync(filename, 'utf8'));
};
// error handling
function readJSONSync(filename) {
  var file = fs.readFileSync(filename, 'utf8');
  try {
    file = JSON.parse(file);
  } catch (e) {
    file = e;
  }
  return file;
};
// naive async
function readJSON(filename, callback) {
  fs.readFile(filename, 'utf8', function(err, file) {
    if (err) {
      return callback(err);
    }

    return callback(null, JSON.parse(file));
  });
};
// naive error handling
function readJSON(filename, callback) {
  fs.readFile(filename, 'utf8', function(err, file) {
    if (err) {
      return callback(err);
    }

    try {
      callback(null, JSON.parse(file));
    } catch(e) {
      callback(e);
    }
  };
};
// dont catch errors thrown by callback itself
function readJSON(filename, callback) {
  fs.readFile(filename, 'utf8', function(err, file) {
    var res;

    if (err) {
      return callback(err);
    }

    try {
      res = JSON.parse(file);
    } catch(e) {
      return callback(e);
    }

    callback(null, res);
  };
};

Promises

`readFile` helper

funciton readFile(filename, enc) {
  return new Promise(function(fullfill, reject) {
    fs.readFile(filename, enc, function(err, file) {
      if (err) {
        return reject(err);
      }

      return fullfill(file);
    });
  });
};

Simple Promise

function readJSON(filename) {
  return readFile(filename).then(function(file) {
    return JSON.parse(file);
  });
};
function(filename) {
  return readFile(filename).then(JSON.parse);
};
function readJSON(filename) {
  return readFile(filename).then(JSON.parse);
};
function(filename) {
  return JSON.parse(fs.readFileSync(filename, 'utf8'));
};

More Complex

function doSomething(getX, getY, callback) {
  var x, y;

  getX(function(err, xval) {
    if (err) {
      x = err;
    }

    x = xval;

    if (isError(y)) {
      callback(y);
    } else {
      callback(null, x*y);
    }
  });

  getX(function(err, yval) {
    if (err) {
      y = err;
    }

    y = yval;

    if (isError(x)) {
      callback(x);
    } else {
      callback(null, x*y);
    }
  });
}
doSomething(fetchX, fetchY, function(err, res) {
  console.log(res);
});

async.parallel

function doSomething(getX, getY, callback) {
  async.parallel([getX, getY], function(err, res) {
    if (err) {
      return callback(err);
    }

    callback(null, res[0]*res[1]);
  });
}
function doSomething(promiseX, promiseY) {
  return Promise.all([promiseX, promiseY])
    .then(function(res) {
      return res[0]*res[1];
    });
}

Another example

function transformFile(in, out, callback) {
  fs.readFile(in, 'utf8', function(err, file) {
    if (err) {
      return callback(err);
    }

    fs.writeFile(out, file.replace(/red/g, 'blue'), function(err) {
      if (err) {
        return callback(err);
      }

      callback(null);
    }
  })
}

async.waterfall

function transformFile(in, out, callback) {
  async.waterfall([
    fs.readFile.bind(fs, in),
    function(file, cb) {
      cb(null, file.replace(/red/g, 'blue'));
    },
    fs.writeFile.bind(fs, out)
  ], callback)
}
function transformFile(in, out, callback) {
  return fs.readFileAsync(in, 'utf8')
    .then(function(file) {
      return file.replace(/red/g, 'blue');
    }).then(fs.writeFileAsync);
}

`fs.readFileAsync` and `fs.writeFileAsync` return promises.

Generators

function* count(){
  var x;
  for (x = 0; true; x++) {
    yield x;
  }
}
for (var c of count()) {
  console.log(c);
}
var c = count();
c.next().value // 0
c.next().value // 1
function* fibonacci() {
  var vals = [0, 1];

  while(true) {
    vals = [vals[1], vals[0] + vals[1]]
    yield vals[1];
  }
}
for (var f of fibonacci()) {
  console.log(f);
}
var fib = fib();
fib.next().value // 1
fib.next().value // 2
fib.next().value // 3
fib.next().value // 5
fib.next().value // 8

Spawn

function spawn(makeGenerator) {
    (function () {
        // when verb is "send", arg is a value
        // when verb is "throw", arg is an exception
        function continuer(verb, arg) {
            var result;
            try {
                result = generator[verb](arg);
            } catch (exception) {
                return reject(exception);
            }
            if (result.done) {
                return result.value;
            } else {
                return Q(result.value).then(callback, errback);
            }
        }
        var generator = makeGenerator.apply(this, arguments);
        var callback = continuer.bind(continuer, "next");
        var errback = continuer.bind(continuer, "throw");
        return callback();
    })().done();
}

// taken from Q.async/Q.spawn
spawn(function* () {
  var user = yield getUser();
  updateUser(user);
}

FRP

function movieSearch(query) {
  if (query.length < 3)
    return Bacon.once([]);     // show no results for queries of length < 3
  return Bacon.fromPromise(queryMovie(query));
}

var text = $('#input')
  .asEventStream('keydown')   // stream of keydown events from text-field
  .debounce(300) // limit the rate of queries
  .map(function(event) {    // get input text value from each event
    return event.target.value;
  })
  .skipDuplicates();   // Ignore duplicate events with same text

// React to text changes by doing a lookup with api function movieSearch, 
// creating a new observable with the results.
//
// Make sure to always react to the latest, value even
// if responses from the server arrives out of order

var suggestions =
  text.flatMapLatest(movieSearch);

// Display "Searching..." when waiting for the results
text.awaiting(suggestions).onValue(function(x) {
  if (x) $('#results').html('Searching...');
});

// Render suggestion results to DOM
suggestions.onValue(function(results) {
  $('#results').html($.map(results, showMovie));
});

Honorable Mentions

highland.js

http://highlandjs.org/

Resources

Async Patterns

By Gary Katsevman

Async Patterns

  • 416