npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

callback-hell

v1.1.13

Published

my abstraction for dealing with async functions that must run sequentially or can run in parallel

Downloads

9

Readme

Callback-Hell

Installation

npm install callback-hell --save

Prerequisite Knowledge

the callback-hell library allows for async functions to be easily chained together.

The library expects all functions to expect callbacks which themselves expect input in a specific format:

Wrapped Result

a wrapped result is a simple object, that can contain:

  • a result (a non-null value, mapped from key: result)
  • an error (a non-null value, mapped from key: error)

If a non-null error is present, the wrapped result is always treated as an error value - despite also potentially having a non-null result value.

Fixing non-compliant functions

Common callback signatures are:

var errorCallBack = function(err) { };
var resultCallBack = function(res) { };
var errorResultCallBack = function(err,res) { };

Using wrapper methods provided by the library, we can fix functions that expect the above callbacks as follows:

var h = require('callback-hell');

var errorCallBackFn = function(cb) { cb('error!'); };
var errorCallBackFnFixed = function(cb) { errorCallBackFn( h.ew(cb)); }; //ew - stands for ErrorWrap

var resultCallBackFn = function(cb) { cb('result!'); };
var resultCallBackFnFixed = function(cb) { resultCallBackFn( h.rw(cb)); }; //rw - stands for ResultWrap

var result = null;
var errorResultCallBackFn = function(cb) { cb('error!',null); };
var errorResultCallBackFnFixed = function(cb) { errorResultCallBackFn( h.bw(cb)); }; //bw - stands for Both (Error and Result) Wrap

Write Orders

a write order is a simple object. It contains:

  • a value ( mapped from key value)
  • a key ( mapped from key key)

Using wrapper methods similar to above, we can extend a callback cb, such that any wrapped result value being passed in is first itself wrapped in a write order:

var result = null;
var cbFn = function(cb) { cb('error!',null); };

// in two steps of wrapping: first turning it compliant -> then decorating with a write order:
var wrappedCbFn = function(cb) { cbFn( h.bw(cb)); }; //bw - stands for Both (Error and Result) Wrap
var writeOrderCbFn = function(cb) { wrappedCbFn( h.ww( cb, 'write_order_key' )); };

// or all at once (notice the ordering seems to be reversed):
var writeOrderCbFn2 = function(cb) { cbFn( h.bw( h.ww( cb, 'write_order_key' ) ) ); };

These write orders allow us to cleanly access previous computations when chaining our async functions.

Async Chaining Functions

AsyncSerial

This function calls a list of functions in sequence, one after another. It expects two arguments:

  • a list of functions
  • a final callback to be run

The functions in the list must be of type:

var fn = function( reader, callback ) { };

The reader argument lets us examine the results of previously run async functions in the list that have used write orders to 'save' their results.

This is done by calling the get method on the reader object, which takes a key as an argument.

The final async is passed an object containing all of the write orders that have been executed in the list.

If any of the functions return an error value (in a wrapped result of course), no further async functions in the list are run. The error value is immediately passed to the final callback instead of a dump of the write orders.

AsyncParallel

This function calls a list of functions all at once. It expects two arguments:

  • a list of functions
  • a final callback to be run

The functions in the list must be of type:

var fn = function(callback) { };

Unlike the serial function, we don't expect a reader - as it doesn't make sense in a parallel context.

The final callback is called immediately if any of the functions error - it is passed the error value.

Alternatively, upon successful completeion of all functions, a dump of the write orders is instead passed to the final callback (like in AsyncSerial

Utils

Also provided are a collection of utilities:

Additional wrapper functions exist which aid in modifying results before they are passed to the wrapped callback:

// h.mw (or MapWrap), allows us to modify the wrapped result (if present, i.e. if not an error), by using
// a specified 'mapping' function:
var wrappedCbFn = function(cb) { cbFn( h.rw (h.mw( cb, function(x) { return x['foo']; } ) ) ); };

// h.ix (or IndexWrap), is a common case of map wrap. It is used to extract a value from an object using a specified key
var wrappedCbFn2 = function(cb) { cbFn( h.rw ( h.ix( cb, 'foo' ) ) ); }; 

Also, functions exist which let you examine the status of a wrapped result:

var err = h.mkError('error wrapped result');
h.isError(err); //evaluates to true

Code Examples

Toy database library:

var db.get( sql, params, callBack ) // function(err,res) { ... };
var db.insert( sql, params, callBack ) // function(err) { ... };   

Account Exists -> Parallel Insert

Code that checks the existence of an account - and based on its existence, adds some entries to the db

var h = require('callback-hell');
var _ = require('underscore');

var accountId = 'foo';
var valsToAdd = [1,2,3,4,5,6];

// create parallel async funcs for the insert - we don't care what order they're run. 
var insertFns = _.map( valsToAdd, function(x) {
      return function(cb) { db.insert( 'insert into vals value ( ?, ? )', [accountId, x ], h.ew( cb )); };
});

var mainFns = [
   // first lift the value into a wrapped result -> then -> index the value using the key 'num' -> then -> wrap the value in a write order with the key 'num'
   function(_r,cb) { db.get( 'select count(*) as num from accounts where account_id = ?', [accountId], h.rw( h.iw( h.ww(cb, 'num'), 'num' ))); },
   function(re,cb) { 
      if( re.get("num") === 0)
         cb(h.mkError( "account doesn't exist!" );
      else
         cb(h.mkNull());
   },
   function(_r,cb) { h.asyncParallel( insertFns, cb ); }
];

h.asyncSerial(mainFns,function(w) {
      if(h.isError(w))
      console.log(w.error);
      });

Account Exists -> Parallel Get

Code that checks the existence of an account - and based on its existence retrieves some entries from the db

var h = require('callback-hell');
var _ = require('underscore');

var accountId = 'foo';
var keys = [1,2,3,4,5,6];
var mapFn = function(x) { return x[0].val; };

// create parallel async funcs for the insert - we don't care what order they're run. 
var getFns = _.map( keys, function(x) {
      // first lift the value into a wrapped result -> then -> map the value using the mapping fn (get the val from the first row) -> then -> wrap the value in a write order with the key equal to the search key
      return function(cb) { db.get( 'select val from key_vals where account_id = ? and key = ?', [accountId, x ], h.rw( h.mw( h.ww(cb, x ), mapFn ))); };
});

var mainFns = [
   function(_r,cb) { db.get( 'select count(*) as num from accounts where account_id = ?', [accountId], h.rw( h.iw( h.ww(cb, 'num'), 'num' ))); },
   function(re,cb) { 
      if( re.get("num") === 0)
         cb(h.mkError( "account doesn't exist!" );
      else
         cb(h.mkNull());
   },
   function(_r,cb) { h.asyncParallel( insertFns, h.ww(cb,'vals') ); }
];

h.asyncSerial(mainFns,function(w) {
      if(h.isError(w))
      console.log(w.error);
      else
      console.log(w.result.vals); //an object mapping keys to values: { 1 : ?, 2 : ? ... }
});