Skip to content
Jean Hugues Robert edited this page Jan 10, 2014 · 250 revisions

What is this?

Alpha code! API is not stable yet (may 2013, v 0.2 january 2014).

Parole object are functions, extended functions, typically used as callbacks.

They are a tool to implement various flow control mechanisms, including publisher/subscribers, steps, generators, asynchronous functions, pipes and promises.

When the parole function is called, other callbacks, attached to the parole function, may be called. Parole objects add a level of indirection: paroles are callbacks that involve other callbacks.

If callbacks are attached to a Parole function appropriately (ie using .then()), the Parole function, when called, will fulfill a promise (Promise/A compliant). See also the introduction.

var read = Parole(); fs.readFile( "test.txt", "utf8", read ); // 'read' is a function
read.then( ..ok.., ..ko.. ); // 'read' is also a promise

The Parole API is available in two forms, integrated in l8 and standalone. The standalone version has a small footprint, file whisper.js is about 6 Kb once minified and compressed.

var P = require( "l8/lib/whisper" );
var timeout = P(); setTimeout( timeout, 1000 );
timeout.on( function(){ console.log( "Time's out!" ); } );
var l8 = require( "l8/lib/parole" );
var P = l8.parole;
var timeout = P(); setTimeout( timeout, 1000 );
timeout.on( function(){ console.log( "Time's out!" ); } );

When file whisper.js is loaded in a browser, it defines a global Parole variable. You are then free to var P = Parole; if you wish. Note: you may want to use uglifyjs and gzip to minimize the size of that file. The file has no external dependencies.

There are live examples available on JsFiddle. There are some basic benchmarks about promises on JsPerf here and some comparisons here, here and here. They all run much faster when using some kind of setImmediate() instead of setTimeout(), see this solution from Domenic.

Table of content

Callbacks

P( ... )

Create a new Parole function.

When called with no parameters p = P(); simply creates a new Parole function. When called with a parameter p = P( x ) is equivalent to p = P.when( x ).

The returned Parole function has a f( ...rslt ) signature, like most node.js callbacks. Calling f( ...rslt ) will appropriately trigger the callbacks attached to the parole. Callbacks are attached with parole.on(), parole.subscribe(), parole.will() and parole.then().

When a parole is used as a promise, using p.then(), the f( ...rslt ) parole function, when called without any parameters, var result = f();, will return the resolved value or it will throw the rejection reason. Note: if result = f() is called before the promise was either resolved or rejected, it resolves the promise with the undefined value and returns undefined.

Note: For compatibility with other Promise/A librairies (Q, When, rspv...) Parole.defer() is also provided.

var q = Q.defer();
var p = P.defer( q.promise ).will( function( err, val ){
  console.log( err ? "Error: " + err : "Success: " + val );
  this.resolve( err ? "default" : val );
});
p.then( function( val ){ console.log( val ); } );

parole.on( callback )

Specify a callback to call whenever the Parole function is called.

That callback is called with the parameters used to call the Parole function, it is called synchronously. Each time the Parole function is called, the callback is called too. That type of callback is sometimes called a "listener" callback.

If the Parole function was called before the callback is installed, that callback will be called immediately when installed. This means that it is not mandatory to attach the callback when the Parole is created, before it is called; it is possible to attach the callback after the Parole function is called, without any loss (iff the parole function was called once, IE only the last call is memorized).

Inside the callback, the this variable references the current Parole function. The value returned by the callback is ignored.

var log = P();
log.on( function(){ console.log.apply( console, arguments ); }
log( "Hello", "World" );

If parole.on() is called multiple times, each call overrides what the previous call did, IE only one callback can remain attached, only one callback will ever be called when the Parole function is called. Parole.on() always returns the parole itself. Note: if you need multiple .on() to be active at the same time, please use p.subscribe( xxx ) instead of p.on( xxx ).

var publish = Paroles();
publish.on( function( msg ){ console.log( "1:" + msg; } );
publish.fork().on( function( msg ){ console.log( "2:" + msg; } );
// Send message to all subscribers
publish( "Hello world!" );

If the parole is chained with additional step, the callback is called with the result of the last step in the chain.

parole.fill( ...args )

Produce results

p.fill( ...x ) is equivalent to the more direct p( ... ). When a parole is filled with some inputs, that input can trigger a .on( cb ) registered callback and execute a .will( f ) step or fulfill a .then() promise.

parole.method( fn, target, args )

Invoke a method to fill a parole.

This calls (synchronous) the specified function on the specified target with the specified args plus the parole function, in order to fill that parole. An exception rejects the parole.

Note: this works only with async functions that receive their callback parameter as the last parameter.

var read = P().method( fs.readFile, fs, [ "test.txt", "utf8" ] );
read.on( function( err, rslt ){ ... } );

Subscribers

Subscribers are callbacks that are invoked when a parole is fulfilled. Whereas only one listener can be attached using p.on( listener ), multiple subscribers can be attached using p.subscribe( f ). Note: the listener and the subscribers coexist.

parole.subscribe( fn )

Add another subscriber callback.

This is sugar for parole.fork().on( fn ).

var publish = P();
publish.on( function( msg ){ console.log( "Listener: " + msg ); } );
publish.subscribe( function( msg ){ console.log( "Subscriber1: " + msg ); } );
publish.subscribe( function( msg ){ console.log( "Subscriber2: " + msg ); } );
publish( "Hello" ) // => 3 messages are logged

Chains of steps

A parole chain is made of "will" steps that are linked parole functions. When the chain executes, the output of a step becomes the input of the next one.

The first step in the chain is the "chain input parole", it is the "entry point" for the chain.

The last "will" step in the chain is the "chain output parole", it is the "exit point" for the chain. Whenever a new step is added to the chain, it becomes the new last step.

The last step in a chain can be linked to one or many promises, using p.then(). These promises linked after the last step of a chain are not part of the chain, however they do depend on the output of the chain.

Each parole of a chain references the chain it belongs to. Please use this.entry() to access the first step of that chain, the "entry step" ; this is handy to then store data associated to the chain.

When a parole chain executes, the result of each step becomes the input of the next step. The result of a step is produced by the step function by calling this( ...results ) or using p.fill( ...results ).

parole.will( f )

Add a step at the end of the parole chain.

It adds a step at the end of the chain and then returns the first parole in the chain. A parole chain is a chain of will parole steps. When the chain executes, the output of a step becomes the input of the next one.

Steps in the chain are linked in such a way that the tail of a chain can be piped into the head of another one, using p.from() and p.pipe().

Inside the f() function, this references the next Parole function. Note : if this was bound, using f.bind( something...) for example, the next Parole is still available, using Parole.next.

When this( ...x ) is called, the parameters becomes the input of that next parole. The next parole can be another .will() chained step, a .then() promise, a .on() listener or a .subscribe() subscriber.

Note: the first p.will( f ) of a parole chain is executed immediately. To avoid that, please use p.from().will( f ) instead ; the f function will then be called when the parole function itself is called.

function load_cfg( fname ){
  return P().will( function(){
    fs.readFile( fname, "utf8", this );
  }).will( function( err, content ){
    if( err ) return this.conclude( null, "global_default" );
    fs.readFile( content + ".cfg" , "utf8", this );
  }).will( function( err, content ){
    this( null, err ? "local_default" : content );
  })
}
var config = load_cfg( "test.cfg" );
config.then( function( cfg ){ console.log( "config: " + cfg ); } );

Note: calling this.will( ...x ) from within the step function is valid, it inserts a new step at the end of the chained "will" steps, FIFO order.

To some extend p.will( f ) is similar to p.then( f1, f2 ) because both allow chaining. The main differences are:

  • will'f signature is f( err, ...results ) whereas then's are f1( err ) and f2( results ).
  • will's f outcome is signaled by calling this( ... ) whereas then's f1 and f2 outcome is their returned value (that can be another promise) or thrown exception.
  • then can produce a single outcome only, whereas will can generate multiple outcomes, by calling this( ...results ) multiple times.
  • will's callbacks are executed synchronously whereas then's ones are executed by the event loop.
  • within then's f1 and f2 this is the current parole whereas in will's f 'this' is the next parole. Note: Promise/a does not specify what this should be, please don't assume that all Promise/a implementations behave like paroles.

parole.may( f )

Like will() unless an error has occurred.

p.may( f( p1, p2, p3...){...} ) is sugar for `p.will( f( err, p1, p2, p3...){ if( err )return this( err ); ... } ). It basically means that the step is skipped on error.

var read_to_uppercase = P.define( function( file ){
  this.will( function(){
    fs.readFile( file, "utf8", this );
  }).may( function( content ){
    this.resolve( content.toUpperCase() );
  })
});
read_to_uppercase( "test.txt" ).then( function( UP ){ console.log( UP ); } );
// or read_to_uppercase( "test.txt", function( err, UP ){ if( !err ) console.log( UP ); } );

parole.wills( f )

Like will() but flattens the last array parameter.

Parole.will( function(){
  this( "hello", [ "world", "!" ] );
}).wills( function( h, w, e ){ console.log( h, w, e ) }; );
var users = P().will(
  // Loads two files in parallel (as per creationix/step example)
  function() {
    var read1 = P(); fs.readFile( __filename,    read1 );
    var read2 = P(); fs.readFile( "/etc/passwd", read2 );
    this.collect( read1, read2 );
  }
).wills(
  // Show the result when done
  function ( err, code, users ) {
    if ( err ) throw err;
    console.log( code );
    console.log( users );
    this.resolve( "ok" );
  }
);
users.then( ok, ko );

parole.mays( f )

Like .wills() but skipped on error.

This is to wills() what may() is to will(), it assume that the last parameter is an array that needs to be flatten.

parole.partial( parameters... )

Preset some of the parole fulfillment callback's parameters.

The returned value is a Promise/A object that is also a function. It is not a full-blown Parole function however.

var success = P().partial( null, "ok!" );
setTimeout( success, 1000 );
success.then( function( ok ){ console.log( ok ); } );
var users = P().will(
  fs.readFile( __filename,  this );
}).will( function( err, content ){
  if( err ) return this( "", err );
  fs.readFile( "/etc/passwd", this.partial( content ) );
}).will( function( content, err, passwd ){
  this.resolve( [ content, err ? "" : passwd ] );
});
users.then( function( r ){
  console.log( __filename   + ": " + r[ 0 ] );
  console.log( "etc/passwd" + ": " + r[ 1 ] );
});

parole.conclude( err_or_null, opt_result )

Exit a parole chain of "will" steps.

When a parole chain is created using parole.will(), parole.conclude() produces the final result for the chain, ie additional steps in the "will" chain are skipped. If the first parameter is a promise, it is that promise's outcome that will conclude the chain.

var p = P().will( function first_step(){
  op1( xxx, this );
}.will( function second_step( a, b, c ){
  if( f1( a, b, c) )return this.conclude( null, "ok!" );
  op2( yyy, this );
}).will( function third_step( err, x, y, z ){
  if( err )return this( err );
  if( f2( x, y, z ) ) return this( new Error( "error" ) );
  op3( zzz, this );
});
...
p.then( f_ok, f_ko );

parole.jump( step, new_parameters... )

Jump into a step in the "will" chain.

If the "step" parameter is not provided, the top of the chain becomes the next step. When the optional parameters are provided, they become inputs for the specified next step.

var loop = P().will( function( n ){
  console.log( "Loop: " + n );
  setTimeout( this, 1000 );
}).will( function(){
  if( --n ) return this.jump( n )
  this.resolve()
}, 10 );

Nested loops are possible.

var p = P();
var label1;
var label2;
p.will( function(){
  console.log( "Entering outer loop" );
  label1 = this( 3 );
}).will( function( n_out ){
  console.log( "Entering inner loop for outer loop " + n_out );
  label2 = this( n_out, 10 );
}).will( function( n_out, n_in ){
  console.log( "Inner loop " + n_in );
  if( --n_int ) return this.jump( label2, n_out, n_in );
  this( n_out );
}).will( function( n_out ){
  if( --n_out ) return this.jump( label1, n_out );
  this.resolve( "done" );
});
p.then( function( r ){ console.log( "Loop " + r ); };

parole.fork( ... )

Create a fork.

When called with no parameters, p.fork() adds a new fork at the end of the parole chain. When multiple forks are added, they will all receive the output of the parole chain as input. This is convenient to execute multiple parallel .will() or .on() steps when a parole chain completes.

var read = P(); fs.readFile( f, "utf8", read );
read.fork().on( function( err, content ){ console.log( err || "No error" ); };
read.fork().on( function( err, content ){ console.log( err ? "No content" : content ); }
read.then( ..ok.., ..ko.. );

When called with parameters, p.fork( ...xxx ), .will( ...xxx ) is called on the forked branch. The result of such multiple forks is an array of fulfilled promises (either solved or rejected), in fulfillment order. That result is prefixed with a null result (no error), that's convenient to fulfill a promise if so desired.

var reader = P.from().will( function( names ){
  for( var name in names ){
    this.fork( function( fn ){
      fs.readFile( fn, "utf8", this );
      this.will( function( err, content ){ this( null, err ? "default" : content ); });
    }, name );
  }
  this();
});
reader( [ "f1", "f2", "f3" ] )
.then( function( list ){ return P.each( list, function( r ){ return r.value; } ) } )
.then( function( values ){ console.log( values ); } );

Generators & functions

Parole generators are functions and are similar to the ECMAScript 6 notion of a generator: a function that can "yield" multiple results. You first need to define a generator. Then you instantiate it. Then you consume its results.

P.generator( fn, [...args] )

Define a generator function.

It returns a function, a "generator constructor", that, when called, will create a new instance of the generator, that instance is a function too.

That new function is a f( ...args, callback ) function. When called, the generator instance will produce a result and invoke the specified callback to deliver it. Its optional parameters, if provided, are sent to the fn function specified when the generator was initially defined.

The "fn" function receives a this that it can add will step to. The output of the such defined chain will be the default output of the generator instance. However, when the instance wants to generate multiple results, it can do so using this.yield( ...result ).

var succ = P.generator( function( seed ){
  this.will( function(){
    this.yield( seed++ );
  }).will( P.jump ); // Loop to start of chain, IE keeps generating results forever
});

function log( msg ){ console.log( msg ); }
var succ5 = succ( 5 );
succ5( log ); // outputs 5
succ5( log ); // outputs 6
succ5( log ); // outputs 7

The optional arguments, if provided, are sent to the generator's next step:

var succ = P.generator( function( seed, increment ){
  this.will(
    function( new_inc ){
      if( new_inc ){ increment = new_inc; }
      var current = seed;
      seed += increment;
      this.yield( current );
    }
  ).will( function( new_inc ){ this.jump( new_inc ); });
});

function log_succ( msg ){ console.log( "Succ: " + msg );}
var succ_exp = succ( 5, 10 );
succ_exp( 10,   log_succ ); // outputs 5, change increment
succ_exp( 100,  log_succ ); // outputs 15, change increment
succ_exp( log_succ );       // outputs 115, don't change increment
succ_exp( log_succ );       // outputs 215

Note: if the parole chain does not use this.yield() it is simply the output of the parole chain that becomes the single output of the generator.

var read2 = P.generator( function( f1, f2 ){
  this.will( function(){
    fs.readFile( f1, "utf8", this );
  }).will( function( err, content ){
    if( err ) return this.resolve( err );
    fs.readFile( f2, "utf8", this.partial( content ) );
  }).will( function( content1, err, content2 ){
    if( err ) return this.resolve( "" );
    this.resolve( content1 + content 2 );
  })
});

var p = P(); read2( "a.txt", "b.txt )( p );
p.then( function( contents ){ ... } );

This case, where only one result is generated, can be simplified using P.define()

P.define( f )

Define a parole chain based asynchronous function.

The value returned by a call to P.define( f ) is a f2 function with a f2( ...args, [callback] ) signature. The value returned by that f2() function is a Promise/A object. As a result the f2() function is compatible both with a callback base style of async function calls and with a promise based style.

var read2 = P.define( function( f1, f2 ){
  this.will( function(){
    fs.readFile( f1, "utf8", this );
  }).will( function( err, content ){
    if( err ) return this( null, err );
    fs.readFile( f2, "utf8", this.partial( content ) );
  }).will( function( content1, err, content2 ){
    if( err ) return this.resolve( "" );
    this.resolve( content1 + content 2 );
  })
});

read2( "a.txt", "b.txt" , function( _, rslt ){ console.log( "contents: " + rslt ) };
// or: read2( ... ).then( function( rslt ){ ... } );

parole.yield( ...results )

Generate a result.

This function is only available in a P.generator( f ) defined generator. When called, it produces a result and then waits until that result is delivered before moving to the next step in the parole chain, potentially providing that step some input specified by the result consumer.

If the result is expected to fulfill a promise, it should specify an error xor null and the resolved value.

// Deliver random numbers, within a specified range, after that random ms delay
var delayed_random = P.generator( function( min, max ){
  this.will( function(){
    var r = min + Math.floor( Math.random() * (max - min) );
    setTimeout( this.partial( r ), r );
  this.will( function( r ){
    this.yield( null, r ) );
  }).this.will( function(){ this.jump(); } ); // eqv this.will( P. jump );
});

var random1000 = delayed_random( 0, 1000 );
random1000( function( _, ms ){ console.log( "" + ms + " ms later." ); };
var n = P(); random1000( n );
n.then( function( r ){ console.log( "Random 0..999: " + r );}

Pipes

Piping is about feeding the input of a parole chain using the output of another one.

In a broader sense, l8 pipes are objects that implement two methods: obj.push(...args) and obj.pipe( pipe ). Calling pipe.push(...args) provides input to that pipe. Calling p1.pipe( p2 ) asks pipe p1 to call p2.push(...args) whenever pipe p1 produces some output.

This is a very simple protocol with no flow control, a destination pipe cannot limit the speed of the code that calls it's .push() method, unless some additional protocol is agreed on between the destination and the source.

Parole objects do implement both .push() and pipe(). They do it in the such a way that calls to .push() are queued and buffered so that no input is provided to a parole chain until that parole has produced a result for a previous input. Note: to achieve this while implementing some kind of "empty output", a Parole pipe can either output null or some other sentinel value, whatever protocol is chosen is application specific.

parole.pipe( ... )

Feed a parole chain with the outputs of another one.

When called with a consumer parole, parole.pipe( consumer ) will feed the consumer with the output of the parole's chain. The consumer is either another pipe or a function. If it is a pipe, consumer.push( ...results ) is called. If it is a function consumer( ...results ) is called`.

x.pipe( y ) returns y, making pipe chaining easy, p1.pipe( p2 ).pipe( p3 ).pipe( p4 )....

var trace = P.from().will( function( msg ){ console.log( msg ); this( msg ); } );
function get_file_lines( name ){
  var out = P();
  var read = P(); fs.readFile( name, "utf8", read );
  read.upgrade( "" ).then( function( content ){
    var lines = content.split( "\n" );
    for( var ii = 0 ; ii < lines.length ; ii++ ){
      out( lines[ ii ] );
    }
  } );
  return out;
}
get_file_lines( "test.txt" ).pipe( trace );

When called with no parameters, parole.pipe() returns a new parole that serializes the production of the other parole, ie the other parole is invoked only when the previous invocation produced a result.

var log = P.from().will( function( msg ){
  setTimeout( this.partial( msg ), 1000 );
}).will( function( msg ){
  this( "!" + msg + "!" );
}).pipe();
var log2 = P.from().will( function( msg ){ this( "*" + msg + "*" } ).pipe();
log.pipe( log2 ).pipe( function( output ){ console.log( output ); } );
log( "Hello" )( "World!" );

parole.from( ... )

Set the source of feeds for a parole chain.

With no parameters, parole.from() configures a parole to accept future feeds. As a result, the first step added using .will() will not be executed immediately, it will be executed when the parole chain is provided some input values.

var p = P();
p.from().will( ... );

When not applied on an existing parole, P.from() will create a new parole.

var p = P.from().will( function( input ){ async_process( input, this /* the callback */ ); } );

With a parole parameter, parole.from( source_parole ) specifies that the output of another parole chain feeds the specified parole chain. In that case, dest.from( src ) is implemented as src.pipe( dest ).

var p1 = P.from().will( function( msg ){ this( "Msg: " + msg ); } );
var p2 = P.from( p1 ).on( function( msg ){ console.log( msg ); } );
p1( "Hello" )( "world!" );

With some other parameters, parole.from( ...xxx ) feeds the parole chain with the specified parameters.

var log = P.from().will( function( msg ){
  console.log( msg );
  this( msg );
};
log.from( "Hello world!" );

In all cases, x.from( ... ) returns x itself.

Promises

Promises/A+ logo Promises are paroles that can be fulfilled once only. When a parole was fulfilled (when that parole function was called), it is either "resolved" or "rejected". It is "rejected" when the first parameter of the parole function (err) is provided a non false value ; it is "resolved", when there is no such error, using the additional parameters.

parole.then( ok, ko )

Attach "promise" callbacks to the parole function.

This function returns a new Parole object, Promise/A compatible, that is a promise fulfilled using the result of either the ok or ko callback. ok is a f( result ) callback called when the promise is resolved (IE when the parole function is called with a null first parameter, or when p.resolve( value ) is called). ko is a f( reason ) callback called when the promise is rejected (IE when the promise function is called with non null first parameter, or when p.reject( reason ) is called). Promise is either resolved or rejected, never both. If the callback is attached after the promise is already fulfilled, it is called with the promise outcome, no loss.

The promise is fulfilled when the Parole function is called or using parole.resolve()/parole.reject(). The first parameter of the Parole function is the promise rejection reason (IE error), it is null if the promise is not rejected. The second parameter, only when the first is falsy, is the promise's resolved value. f( error_or_null, opt_result ) is similar to the signature for most of the node.js API's callbacks, this is intentional. Note: if the Parole function is called with more than one result, the resolved value of the promise will be an array containing all the results.

The Promise/A specification is accessible there: http://promises-aplus.github.io/promises-spec/

var read = P(); fs.readFile( "test", "uft8", read );
read.then(
  function( ok ){ console.log( "content: " + ok ); },
  function( err ){ console.log( "err: " + err );   }
);

parole.then() returns a new promise. That new promise is said to be "chained" with the previous one because it is fulfilled using the result of the previous promise's ok or ko callback, as specified in the Promise/A specification.

In addition to the Promise/A specification inside the ko callback, this.error contains the rejection reason. Inside the ok callback, this.value contains the resolved value. As a result, a callback can check this.error to detect rejections. This is convenient when parole.then( f, f ) is used, ie when the same callback handles both resolved and rejected cases.

Note: when the error used in parole.reject() is a falsy value, this.error is coerced to {paroleError: err} and the falsy error value is available in this.value. IE, please check this.error to detect errors, use this.value to get the error's value.

parole.resolve( ... )

Resolve a promise with a value.

This is equivalent to calling the parole function with a null first parameter followed by the value parameter, IE this( null, v ) is equivalent to this.resolve( v ). x.resolve( y ) returns x.

If more than one parameter is provided, the parameters are collected into an array. If no parameters are provided, undefined is assumed. The returned value is the promise itself.

parole.reject( reason )

Reject a promise with a reason.

This is equivalent to calling the parole function with the reason as first parameter (assuming the reason is not false). x.reject( y ) returns x.

parole.when( ...args )

Fulfill a parole using one or many values or promises.

When the parameter is not a promise, parole.when( value ), will resolve immediately the parole using the value.

When the parameter is a promise, parole.when( promise ) will either reject or resolve the parole depending on the outcome of the promise. Note: the provided promise can be a parole, a Q promise, a jQuery promise... anything with a .then( ok, ko ) function where ok( value ) xor ko( err ) is called when the promise is fulfilled.

Where there are more than one parameter, parole.when( a, b, c ) will resolve the parole with an array of values (using x.when() on a, b and c...), with each value in the array at the same index as the corresponding parameter If any of the parameters is a promise that is reject, the parold is immediately rejected, using the first rejection reason.

Note: when not applied on an existing parole, P.when( ...x ) will create a new parole.

var resolved = P.when(); // eqv P().resolve();
var resolved_true = P.when( true ); // eqv P().resolve( true );
var read = P(); fs.readFile( "test.txt", "utf8", read );
var on_read = P.when( read );

parole.upgrade( default [, delay] )

Promise a backup value in case of rejection.

Create a new promise than improves upon the original one because it provides the value to use to avoid the original promise potential rejection.

If no default value is provided, false is assumed.

If a delay is provided, the still unfulfilled promise is resolved with the default value.

var read = P(); fs.read( "config.cfg", "utf8", read );
var safe_read = read.upgrade( "default", 4000 );
safe_read.then( function( config ){ console.log( "config: " + config ); } );

Note: when no delay is provided, parole.upgrade( x ) is sugar for parole.then( null, function(){ return x; } ).

parole.timeout( delay [, message ] )

Reject on timeout.

The promise is rejected with a reason that is an Error object whose name is "ParoleTimeout". Note: when applied on an existing promise, it is that promise, not a new one, that will be rejected, unless it is resolved within the specified delay.

The optional message is the message associated to the timeout error. The default message is "Timed out after xxx ms", where xxx is the delay.

var read = P(); fs.read( f, "utf8", read );
read.timeout( 10000 );
read.then( ok, function( err ){
  if( err && err.name === "ParoleTimeout" ){
    console.log( "Read timeout: " + err.message );
  }
  return "default";
});
var read1 = P(); fs.readFile( f1, "utf8", read1 );
var read2 = P(); fs.readFile( f2, "utf8", read2 );
Parole.or( read1.upgrade(), read2.upgrade() ).timeout( 10000 ).then(
  function( first_non_empty ){ ... },
  function( err ){ ... }
});

parole.delay( duration )

Insert a delay before completion.

When applied on an existing promise, p.delay( millisec ), will return a new promise that will be fulfilled with the outcome of the existing one but only after the delay as elapsed.

When called P.delay( duration ), it will return a new promise that will be resolved with undefined after the specified delay.

Arrays of promises

P.each( handler, promises_or_values )

Process an array of promise (or values).

Create a new parole (ie a promise maybe) whose outcome depends on the handler's reaction to the specified promises and values. When applied on an existing promise, it is that promise that is fulfilled, not a new one.

var find = P().each(
  function handler( chunk ){ if( chunk.value === "match" ) return chunk; },
  [ get_a(), get_b(), "xx", get_c() ]
);
find.then( function( value ){
  ...
};

Note: the array can contain values in addition to promises.

There exists some predefined handlers: Parole.join, Parole.select, Parole.collect, Parole.and and Parole.or. The handler first parameter is optional, when it is not provided, the promise is resolved using an array of "chunks" (see below). The order of the parameters can be reversed.

The handler's signature is f( chunk ) where chunk contains the following information:

  • promises: a copy of the initial array of all the promises to be processed.
  • promise: the promise the handler is processing.
  • error: true if the promise was rejected.
  • value: the promise resolved value or rejection reason.
  • list: an array of "chunks", in arrival order, including this one.
  • rank: the rank in arrival order for this chunk.
  • array: an array of chunks, with indexes matching the initial array.
  • index: the index of this chunk in the initial array,
  • results: the so far accumulated results, an array.

The value returned by the handler determines the outcome of the each() function.

  • if the chunk itself is returned, the chunk's error and value set the outcome.
  • if either the chunk's list, array or results property is returned, it sets the outcome.
  • if some other value is returned, it is accumulated inside the results array, until all chunks are processed and then the results array becomes the outcome.
  • if the handler throws an exception, that exception becomes the outcome.
P().each( Parole.or, [ case1, case2 ] ).then( function( r ){
  if( r ){
    console.log( "case1 or case2" );
  }else{
    console.log( "neither case1 nor case2" );
  }
);

Note: Parole.each( xxx ) is equivalent to P().each( xxx ), a Parole function is created. In that case, the value returned by each() is the new promise.

P.and( promise1, p2, p3... )

Create a promise resolved to false unless all the promises resolve to a true value.

When all the promises resolve to a true value, the promise value is the value of the last resolved promise. If one of the promises is rejected, the promise is rejected. If applied on an existing promise, the function will fulfill that promise instead of creating a new one.

var a = P.and( p1, p2, p3 );
a.then(
  function( last ){ console.log( last ? last : "none" },
  function( err   ){ console.log( "A promise failed: " + err ); }
);

P.not( promise1, p2, p3... )

Create a promise resolved to true iff all the promises resolve to a false value.

If any of the promises resolves to true, it resolves to false. If all the promises resolve to false, the promise resolves to true. This is the "nor, not or" logic. If any of the promises is rejected, the promise is rejected with the same reason, unless already fulfilled.

P.not( p1, p2, p3 ).then( function( b )[
  console.log( b ? "Neither p1, nor p2, nor p3" : "Either p1 or p2 or p3" );
});

P.or( promise1, p2, p3... )

Create a promise resolved with the first resolved true value or false.

If one of the promises is rejected, the unresolved promise is rejected. If applied on an existing promise, the function will fulfill that promise instead of creating a new one.

var a = P.or( p1, p2, p3 );
a.then(
  function( first ){ console.log( first || "none" },
  function( err   ){ console.log( "A promise failed: " + err ); }
);

P.nand( promise1, p2, p3... )

Create a promise resolved with true iff any of the promises resolve to false.

If any of the promises is resolved to false, it resolves to true. If all the promises resolve to true, the promise resolves to false. This is the "nand, not and" logic. If any of the promises is rejected, the promise is rejected with the same reason, unless it was already fulfilled.

P.collect( promise1, p2, p3... )

Create a promise resolved using the other resolved values.

If one of the promises is rejected, the promise is rejected. If applied on an existing promise, the function will fulfill that promise instead of creating a new one.

var a = P().collect( p1, p2, p3 );
a.then(
  function( array ){ console.log( array },
  function( err   ){ console.log( "A promise failed: " + err ); }
);

P.select( promise1, p2, p3... )

Create a promise resolved using the first resolved value.

If one of the other promises is rejected before one is resolved, the promise is rejected. If applied on an existing promise, the function will fulfill that promise instead of creating a new one.

var a = P.select( p1, p2, p3 );
a.then(
  function( first ){ console.log( first },
  function( err   ){ console.log( "A promise failed: " + err ); }
);

P.join( promise1, p2, p3... )

Create a promise that is resolved when all the other promises are fulfilled.

The promise's resolved value is an array with all the promises, in fulfillment order.

// Collect all resolved values, ignoring rejected promises
P.join( p1, p2, p3 ).then( function( all ){
  return P.each( all, function( p ){ if( !p.error )return p.value; } );
}).then( function( resolved_values ){
  console.log( resolved_values );
});
function search( request ){
  var parole = P(); 
  process_async_search( request, parole /* f( err, rslt )callback */ );
  // Set default result and timeout
  var safe_parole = parole.upgrade( "not found", 10000 );
  safe_parole.request = request;
  return safe_parole;
}
var search1 = search( engine1 );
var s2      = search( e2 );
var s3      = search( e3 );
var searched = something;
// Wait for all searches to complete
P.join( search1, s2, s3 )
// Pick first matching promise
.then( function( results ){
  return P.each( results, function( result ){
    if( result.value !== searched ) return;
    result.value = result.promise;
    return result;
  }
})
// Display result
.then( function( matching_promise ){
  if( matching_promise !== [] ){
    console.log( "Found " + searched + " using " + matching_promise.request );
  )else{
    console.log( "Not found" );
  }
});

Misc

P.scheduler( setImmediate_style_function )

Configure the global scheduler used to queue delayed functions.

The parameter is either null (restore default) or a f( fn ) style function. Note: setTimeout is ok, a 0 parameter is passed. The scheduler function is expected to execute the queued functions in FIFO order.

The default scheduler is either setImmediate(), process.nextTick() or setTimeout(). Some librairies provide more efficient implementations, see for example https://github.com/NobleJS/setImmediate or google "nextTick setImmediate" to find other solutions.

P.scheduler( setTimeout );

P.schedule( fn [, target [, args]] )

Enqueue a function to execute during the global event loop.

This is similar to the setTimeout( fn, 0 ) thing but with some buffering to make it run faster. Functions registered using .schedule() will execute in FIFO order. Note: because of the buffering, functions scheduled using .schedule() may run before other functions registered earlier using setTimeout() or other means.

ECMA6 compatibility

(v 0.2 only)

To create ECMA6 compatible promises, please use p = P.Promise( f ) instead of p = P().

To use another ECMA6 compatible libairie, set the promise factory using p.factory( Promise ). Some libraries run faster or provide better support for debugging than the default one. Bluebird is one of them.