A more OO JavaScript Promises/A spec implementation
Switch branches/tags
Nothing to show
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
spec
src
.gitignore
LICENSE.md
README.md
package.json

README.md

Postpone

Postpone is a sugar-free, more OO JavaScript lib implementing the Promises/A specification.

There is no syntactic sugar in this library and that is for 2 reasons:

  • syntactic sugar is NOT necessary!
  • syntactic sugar has a cost in terms of maintenance.
  • syntactic sugar slows down the process of learning the functioning of a new library as it introduces useless noise that take the focus away from the core concepts.

As a result of this approach, whatever can be done using postpone, can be done in one way and one way only!

How promises work

Here's a list of rules that describe how promises behave:

  1. Given a promise p, by calling the method p.then(successCallback, failureCallback) we always obtain a new promise q (in other words then returns a new promise). Such promise q is chained to p in what we will call "the promises chain". You will find out all the implications of such state of affair in the following points.

  2. A promise p can be either resolved p.resolve(value) or rejected p.reject(reason).

  3. Resolving or rejecting a promise implies the same action been taken on the whole promise chain. So if we obtain r from q by doing r = q.then(successCallback, failureCallback) and q from p by doing q = p.then(successCallback, failureCallback), then the chain p->q->r gets resolved one promise after the other when p.resolve() gets executed (now this is not entirely true, see point 5 for more details).

  4. When propagating a promise resolution, all the success callbacks in the line (or tree) will be invoked and each callback will be in charge of passing on the value to the next generation of promises.

  5. When propagating a promise resolution, each success callback can interrupt the success stream by throwing an exception (which will cause the next available failure callback to be executed).

  6. When propagating a rejection, only the first failure callback will be invoked.

  7. When propagating a rejection, if no failure callback is provided in the line, then the exception will emerge at the global level and interrupt the program by going uncaught.

Examples

The foundations

Here's how you generically leverage the power of promises:

function getPromiseThatWillBeResolvedWithValue(value) {
   var p = new postpone.Promise();
   process.setTimeout(function() {
      p.resolve(value);
   }, 1000);
   return p;
}

function getPromiseThatWillBeRejectedWithReason(reason) {
   var p = new postpone.Promise();
   process.setTimeout(function() {
      p.reject(reason);
   }, 1000);
   return p;
}

var p = getPromiseThatWillBeResolvedWithValue(42);
var q = getPromiseThatWillBeRejectedWithReason(new Error("Too bad!"));

p.then(function(value) {
   // V - this will execute
}, function(reason) {
   // X - this will not execute
});

q.then(function(value) {
   // X - this will not execute
}, function(reason) {
   // V - this will execute
});

The chain

As we know though, promises can be chained too:

getPromiseThatWillBeResolvedWithValue(42)
   .then(function(value) {
      console.log(value); // -> 42
      return 1;
   })
   .then(function() {
      console.log(value); // -> 1
   })
   .then(function() {
      console.log(value); // -> undefined
   });

As mentioned in rules 3 and 4, each promise is responsible for letting the next promise know what the provided success value was. In the example above the first promise change the initial return value to 1 while the second one gets a bit lazy and does not even return a value. As a result the third promise gets an undefined.

When the initial promise gets rejected, the chain behaves a little differently (as per rule 6):

getPromiseThatWillBeRejectedWithReason(new Error("Too bad!"))
   .then(null, function(reason) {
      console.log(reason.message); // Too bad!
   })
   .then(null, function() {
      // X - this will not execute
   })
   .then(null, function() {
      // X - and this won't either
   });

As expected, propagation stops after the first failure callback gets executed.

Controlling the propagation along the chain

The propagation along the chain can be controller as described in rule 5. Here's how:

getPromiseThatWillBeResolvedWithValue(42)
   .then(function(value) {
      console.log(value); // -> 42
      throw new Error("Let's make it a failure now!");
   })
   .then(function(value) {
      // X - despite what you might think this will not execute
   }, function(reason) {
      // V - while this will!!!
   });

The initial promise gets resolved but then, because we have thrown an exception from inside the callback, the next promise will be rejected and its callback managing failures will be invoked instead. We moved from a success to a rejection moving along the chain.

Static (very useful) methods

postpone comes equipped with a couple of static methods that will make your life easier when dealing with multiple promises at the same time.

All

all returns a promise that will be resolved when all the input promises get resolved or rejected as soon as any one of the input promises get rejected:

var a = getPromiseThatWillBeResolvedWithValue(1);
var b = getPromiseThatWillBeResolvedWithValue(2);
var c = getPromiseThatWillBeResolvedWithValue(3);
postpone.Promise.all([a, b, c]).then(function(values) {
   // values is the array [1, 2, 3]
});
Any

any returns a promise that will be resolved as soon as one of the input promises get resolved or rejected when all the input promises get rejected:

var a = getPromiseThatWillBeRejectedWithReason("reason 1");
var b = getPromiseThatWillBeResolvedWithValue(42);
var c = getPromiseThatWillBeRejectedWithReason("reason 2");
postpone.Promise.any([a, b, c]).then(function(value) {
   // value is 42
});
Promisify

promisify transforms a function that perform an asynchronous task in a promise. Normally this functions take a list of parameters the last of which is callback to be executed when the async operation is terminated. Here's an example using the notorious fs.readFile nodejs function:

var promisified = postpone.Promise.promisify(fs.readFile);
promisified("a-file.txt", {
   encoding: "utf8"
}).then(function(fileContent) {
   console.log(fileContent);
});

Note that, for promisify to work properly, the callback accepted by the original function as last parameter must have he following signature:

function(error, value);

Outro

And that was me on promises.

Cheerio