Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

parley

Package Overview
Dependencies
Maintainers
2
Versions
49
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

parley

Practical, lightweight flow control for Node.js. Supports callbacks and promises.

  • 1.0.1
  • Source
  • npm
  • Socket score

Version published
Weekly downloads
17K
decreased by-11.25%
Maintainers
2
Weekly downloads
 
Created
Source

parley

Practical, lightweight flow control for Node.js

Usage

Parley helps you write functions that can be called like this:

doStuff({ foo: 123 })
.set({ bar: 456 })
.exec(function (err, result){

});

Or like this:

doStuff({ foo: 123 })
.set({ bar: 456 })
.then(function (result){

})
.catch(function(err) {

});

You can also obtain a promise simply by calling .toPromise().

Help

If you have questions or are having trouble, click here.

Bugs   NPM version

To report a bug, click here.

Overview

This section offers a high-level look at how to use parley from both a userland and implementor perspective. You can also skip ahead to the API reference below.

Building a deferred object

Use parley to build a deferred object. This provides access to .exec(), .then(), .catch(), and .toPromise(), but you can also attach any extra methods you'd like to add.

var parley = require('parley');

var deferred = parley(function (done){
  setTimeout(function (){
    if (Math.random() > 0.5) {
      return done(new Error('whoops, unlucky I guess'));
    }
    if (Math.random() > 0.2) {
      return done(undefined, Math.floor(5*Math.random()));
    }
    return done();
  }, 50);
});

For a more complete version of the above example, click here.

Results

To send back a result value from your handler, specify it as the second argument when invoking done.

return done(undefined, 'hello world');

Depending on how userland code chooses to work with the deferred object, your result will be passed back to userland as either the second argument to the .exec() callback, or as the value resolved from the promise.

// Node-style callback
.exec(function(err, result) {
  // => undefined, 'hello world'
});

// or promise
.then(function(result) {
  // => 'hello world'
});

Errors

To send back an error from your handler, handle it in the conventional Node.js way.

return done(new Error('Oops'));

Depending on how userland code chooses to work with the deferred object, your error will be passed back to userland as either the first argument to the .exec() callback, or as the promise's rejection "reason".

// Node-style callback
.exec(function(err, result) {
  // => [Error: oops], undefined
});

// or promise
.catch(function(err) {
  // => [Error: oops]
});
Negotiating errors

Sometimes, there is more than one exceptional exit a function might take. To make it possible for userland code to negotiate different exits from your function, give the error a code property.

var x = Math.random();

// Miscellaneous error (no code)
if (x > 1) {
  return done(new Error('Consistency violation: This should never happen.'));
}

var flaverr = require('flaverr');
// Other recognized exceptions
if (x > 0.6) {
  return done(flaverr('E_TOO_BIG', new Error('Oops: too big')));
}
if (x < 0.4) {
  return done(flaverr('E_TOO_SMALL', new Error('Too small -- probably already in use!')))
}

Then in userland, this can be easily negotiated. Note that whether the code is using a Node-style callback or a promise, the approach is conceptually the same regardless.

// Node-style callback
.exec(function(err, result) {
  if (err) {
    switch(err.code) {
      case 'E_TOO_BIG': return res.status(400).json({ reason: 'Ooh, too bad!  '+err.message });
      case 'E_TOO_SMALL': return res.status(401).json({ reason: 'Please try again later.  '+err.message });
      default:
        console.error('Unexpected error:',err.stack);
        return res.sendStatus(500);
    }
  }//-•

  // ...
});
// Promises
.then(function (result) {
  // ...
})
.catch({ code: 'E_TOO_BIG' }, function(err) {
  return res.status(400).json({ reason: 'Ooh, too bad!  '+err.message });
})
.catch({ code: 'E_TOO_SMALL' }, function(err) {
  return res.status(401).json({ reason: 'Please try again later.  '+err.message });
})
.catch(function(err) {
  console.error('Unexpected error:',err.stack);
  return res.sendStatus(500);
});

Flow control

Since Node.js is asynchronous, seemingly-tricky flow control problems often arise in practical, userland code. Fortunately, they're easy to solve when equipped with the proper tools.

When using Node-style callbacks, use the async package.

var async = require('async');

Most of the examples below use async for simplicity, but note that many similar affordances are available for promises -- for example, check out .toPromise() (below) and Promise.all() (in bluebird, or native in ES6, etc.). The concepts are more or less the same regardless.

Async loops

Loop over many asynchronous things, one at a time, using async.eachSeries().

var results = [];
async.eachSeries(['a','b','c','d','e','f','g','h','i','j','k','l'], function (letter, next) {
  doStuff(letter).exec(function (err, resultForThisLetter){
    if (err) { return next(err); }
    results.push(resultForThisLetter)
    return next();
  });
},
// ~∞%°
function afterwards(err) {
  if (err) {
    console.error(err);
    return res.sendStatus(500);
  }
  return res.json(results);
});
Async "if"

Even simple detours and conditionals can sometimes be tricky when things get asynchronous.

Fortunately, relatively concise and robust branching logic can be easily implemented using out-of-the-box JavaScript using this weird trick™.

User.findOne({ id: req.param('id') })
.exec(function(err, profileUser) {
  if (err) { return res.serverError(err); }
  if (!profileUser) { return res.notFound(); }

  // If the request came from a logged in user,
  // then fetch that user's record from the database.
  (function(proceed) {
    if (!req.session.userId) {
      return proceed();
    }
    User.findOne({ id: req.session.userId })
    .exec(function (err, loggedInUser) {
      if (err) { return proceed(err); }
      if (!loggedInUser) { return proceed(new Error('Logged-in user ('+req.session.userId+') is missing from the db!')); }
      return proceed(undefined, loggedInUser);
    });

  // ~∞%°
  })(function afterwards(err, loggedInUser){
    if (err) { return res.serverError(err); }

    return res.view('profile', {
      profile: _.omit(profileUser, ['password', 'email']),
      me: loggedInUser ? _.omit(loggedInUser, 'password') : {}
    });

  });
});

More background on using the if/then/finally pattern for asynchronous flow control

Async recursion

Much like "if/then/finally" above, the secret to tidy asynchronous recursion is self-calling function.

#!/usr/bin/env node

var path = require('path');
var fs = require('fs');

// Starting from the current working directory, ascend upwards
// looking for a package.json file.  (Keep looking until we hit an error.)
(function _recursively(thisDir, done){

  var pathToCheck = path.resolve(thisDir, './package.json');
  fs.stat(pathToCheck, function(err) {
    if (err) {
      switch (err.code) {

        // Not found -- so keep going.
        case 'ENOENT':
          var oneLvlUp = path.dirname(thisDir);
          _recursively(oneLvlUp, function(err, nearestPJ) {
            if (err) { return done(err); }
            return done(undefined, nearestPJ);
          });
          return;

        // Misc. error
        default: return done(err);
      }
    }//-•

    // Otherwise, found it!
    return done(undefined, pathToCheck);

  });//</ fs.exists >

// ~∞%°
})(process.cwd(), function afterwards(err, nearestPJ) {
  if (err) {
    console.error(err);
    return process.exit(1);
  }

  console.log('Found nearest package.json file at:',nearestPJ);

});

More examples and thoughts on asynchronous recursion

Parallel processing / "races"

To manage "races" between deferred objects while still performing tasks simultaneously, you can use async.each() -- for example, here's the async.eachSeries() code from above again, but optimized to run on groups of letters simultaneously, while still processing letters within those groups in sequential order:

var results = [];
async.each(['abc','def','ghi','jkl'], function (group, next) {

  var theseLetters = group.split('');
  var resultsForThisGroup = [];
  async.eachSeries(theseLetters, function (letter, next) {
    doStuff(letter).exec(function (err, resultForThisLetter){
      if (err) { return next(err); }
      resultsForThisGroup.push(resultForThisLetter)
      return next();
    });
  },// ~∞%°
  function (err) {
    if (err) { return next(err); }

    resultsForThisGroup.forEach(function(letter){
      results.push(letter);
    });

    return next();
  });

},// ~∞%°
function afterwards(err) {
  if (err) {
    console.error(err);
    return res.sendStatus(500);
  }
  return res.json(results);
});

More background on asynchronous vs. synchronous flow control in general

API reference

Implementor interface

parley()

Build and return a deferred object.

As its first argument, expects a function that will run whenever userland code executes the deferred object.

var deferred = parley(function (done) {
  // ...
});

Or, instead of passing in a function, you can pass in a dictionary of options.

var deferred = parley({
  codeName: 'doStuff',
  handleExec: function (done){
    // ...
    return done();
  }
});
OptionData typeDescription
handleExec((function))This is the function that you must provide in order to describe what happens when the deferred object is executed.
codeName((string?))An optional value used to improve readability in error messages. If left unspecified, parley will fall back to the function name of the provided handleExec handler function, if it has one.

Userland interface

The deferred object returned by parley() exposes a few different methods.

.exec()
parley(function(done){ return done(undefined, 1+1); })
.exec(function (err, result) {
  // => undefined, 2
});
parley(function(done){ return done(new Error('whoops'), 1+1); })
.exec(function (err, result) {
  // => [Error: whoops], undefined
});
.then()
parley(function(done){ return done(undefined, 1+1); })
.then(function (result) {
  // => 2
});
.catch()
parley(function(done){ return done(new Error('whoops'), 1+1); })
.catch(function (err) {
  // => [Error: whoops]
});
.toPromise()
var promise1 = parley(function(done){ return done(undefined, 1+1); }).toPromise();
var promise2 = parley(function(done){ setTimeout(function(){ return done(); }, 10); }).toPromise();

Promise.all([
  promise1,
  promise2
])
.then(function(result){
  // => [2, undefined]
}).catch(function (err) {

});

Contributing   Master Branch Build Status   Master Branch Build Status (Windows)

Please observe the guidelines and conventions laid out in the Sails project contribution guide when opening issues or submitting pull requests.

NPM

License

This package, like the Sails framework, is free and open-source under the MIT License.

Keywords

FAQs

Package last updated on 21 Dec 2016

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc