Why Promises

"Servers can easily use vastly more asynchronous code-paths than a typical client-side application. Between database interactions, file I/O, HTTP requests, child processes [...], and more, server-side JavaScript can quickly become an overwhelming web of callbacks if not approached properly. Promises are a powerful tool in dealing with the complexity of composing various asynchronous actions and managing them in a coherent and encapsulated way, with reasonable flow and error handling. Most client-side modules only begin to venture into the full experience of the complex combinations of serial and parallel actions that can be commonplace on a NodeJS server, and can be smoothly handled by promises.[...]"

How it works...

Warning SHOULD BE REWRITTED : don't show ._start and new Promise(). Explain it directly : you never create it directly...

Remarque If you're feeling confortable wih Promises, you could skip directly to the last two sections in this page : 3 famillies and callback return rules.

As Promise Pattern could be challenging to understand without seeing it in action : let's do this directly.

A promise is just a handler where you enqueue some functions that will be executed linearly (chained) :

// normally : you never create a promise as this. 
// Use Deferred or when(...) in place (see below).
// It's just there to show you how it's managed
// under scene.
var promise = new deep.Promise();	

promise
.done(function(success){
	return success + " world";
})
.fail(function(error){
	deep.log("error : ", error);
})
.done(function(success){
	deep.log("success : ",success);
});

If you try this : nothing happens.

That's normal. The promise hasn't launched the execution of its queue. It waits for something to start.

So let's take exactly the same example and add just a sentence :

// normally : you never create a promise as this.
// Use Deferred or when(...) in place (see below).
// It's just there to show you how it's managed 
// under the scene.
var promise = new deep.Promise();

promise
.done(function(success){
	return success + " world";
})
.fail(function(error){
	deep.log("error : ", error);
})
.done(function(success){
	deep.log("final success : ",success);
});

// normally : you never launch by hand a promise.
// use Deferred or when(...) in place (see below).
// It's just there to show you how it's managed
// under scene.
promise.resolve("hello");

Blam... The queue has been executed !


So, what's happened ?

First, you could observe that both "done" have been fired. You could also observe that the value returned by the first done has been seen as success for second done. So callbacks are "wired".

Secondly : the "fail" callback hasn't been fired. It's because for the moment, the value passed through callbacks wasn't an error.

Let's try this :

var promise = new deep.Promise();

promise
.done(function(success){
	deep.log("success 1 : ", success);
	return success + " world";
})
.fail(function(error){
	deep.log("error : ", error);
})
.done(function(success){
	deep.log("success 2 : ",success);
});

promise.reject(new Error("oups! error!"));

There, only the "fail" has been fired, because the value passed through callbacks was an error.

That's it... You've almost all the painting. A promise is just a queue, where you store callbacks, called linearly and selectively when a success or an error is injected.

If you do so, either you're working on core tools, or you missed something. It was just to show you the class itself (you'll never see it again...;)) and the delayed execution.

In place, you'll use a deep.Deferred or deep.when(...).

deep.Deferred

When you need a Promise, you could first use a Promise Distributor called "Deferred". Let's introducing it with asynchrone example :

var def = deep.Deferred();
setTimeout(function(){
	def.resolve("hello from deferred");
}, 1000);

def.promise()
.done(function(success){
	deep.log("success : ", success);
})
.fail(function(error){
	deep.log("error : ", error);
});
var def = deep.Deferred();
setTimeout(function(){
	def.reject(new Error("hello from deferred"));
}, 1000);

def.promise()
.done(function(success){
	deep.log("success : ", success);
})
.fail(function(error){
	deep.log("error : ", error);
});

Okay.. you got the point with deferred. A deferred is just a "promises distributor" that could be either resolved or rejected. When this arrive, it starts all previously distributed promises with its result as success or error. Nothing more.

deep.when

A second good way to obtain a Promise, is to use "when" function. This simple method takes something as argument and returns a promise :

deep.when("hello")
.done(function(success){
	deep.log("success : ", success);
});
deep.when(new Error("oups!"))
.fail(function(error){
	deep.log("error : ", error);
});

In both examples here, the "when" argument has been directly injected in the returned promise.

The interesting point here is that you could give a promise as "when" argument. (let's introduce a new method ".delay( ms )"" which delays the execution of callbacks after it.)

var promise = deep.when("hello delayed world").delay(500);	// async simulation

deep.when(promise)
.done(function(success){
	deep.log("success : ", success);
});

There, the "when" function returns a second promise, that's waiting the resolution of the first one before executing its own queue.

The really interesting point here, is that "when" hides difference between sync and async programmation. Either you provide a "synced" value, and the returned promise is executed directly, either you provide a promise (synced or not) and the returned promise wait the first promise before start. In all cases, code still the same.

deep.all(obj1, obj2, ...)

How mentioning "deep.when( obj )" without mentioning its big brother : deep.all(obj1, obj2, ...).

var prom1 = deep.when("first").delay(100);
var prom2 = deep.when("second").delay(200);

deep.all(prom1, prom2, "third")
.done(function(success){
	deep.log(success);
})

It returns you a single promise that waits for the resolution of all arguments passed. The success injected in returned promise is the array of resolved values. And as you could see, "immediate" values are also managed.

3 famillies and big APIs

Fondamentaly, there is three callback famillies. You already know the two first ones : "done" and "fail". Stills a third one : "always".

done
Runs only if current value(s) holded by the promise is not an Error.
fail
Runs only if current value(s) holded by the promise is an Error.
always
Always runs : the value(s) holded could be anything.
deep.when("hello promise")
.always(function(s, e){
	deep.log("always : s: ", s, " - e : ", e);
})
.done(function(success){
	deep.log("done : ", success);
})
.fail(function(error){
	deep.log("fail : ", error);
});
deep.when(new Error("bye bye!"))
.always(function(s, e){
	deep.log("always : s: ", s, " - e : ", e);
})
.done(function(success){
	deep.log("done : ", success);
})
.fail(function(error){
	deep.log("fail : ", error);
});

Why is this so important to know now ? Simply because the Promise API has been decorated with lot of methods, and Promise based chains also add their own methods. And the fact is that any method from any chain (Promise or others) belong to one of those famillies.

To explain that, let's introduce 3 logging methods : .log(), .slog(), .elog()

.log()
Always familly : will log current chain's success OR error.
.slog()
Done familly : will only log current success (if any).
.elog()
Fail familly : will only log current error (if any).

Let's take previous example and rewrite it with those new methods :

deep.when("hello promise").log("always : ").slog("done : ").elog("fail : ");
deep.when(new Error("bye bye!")).log("always : ").slog("done : ").elog("fail : ");

So when you'll use methods from any deepjs chain, you should always know its familly. To have an idea, lots are "done", few are "always", and really few are "fail".

The "callbacks return" rules

When you handle a success or an error in a callback (from any of the three famillies), the value returned by your callback is analysed and, depending on its nature, it will be seen as success or error for the next handlers.

It means, by example that you could handle an error in a "fail", return something else that will be seen as success, and then continue normally.

deep.when(new Error("oups!"))
.fail(function(error){
	return true;		// we return something else than the error
})
.slog();	// success log (a 'done')

In other words, you've recovered your error.

So, the "callbacks return rules" just say how values are seen depending on there nature.

Nothing (or undefined)

When your callbacks returns nothing (i.e. which is the same undefined), the current "state" of the promise (i.e. its succes or its error) isn't modified.

deep.when("hello")
.done(function(s){
	// doing nothing
})
.log();

An error

When your callbacks returns an error, it become the error holded by the promise.

Remarque it's the same thing when you throw an error.

deep.when("hello")
.done(function(s){
	return new Error("oups!");
})
.log();
deep.when("hello")
.done(function(s){
	throw new Error("oups!");
})
.log();

A Promise

When your callbacks returns a promise, the main promise will wait the resolution of the returned one, and then forward its success or error.

deep.when("hello")
.done(function(s){
	return deep.when("world").delay(1000);
})
.log();

A deep.Undefined

When you want to force undefined injection as success, as by default undefined will be seen as nothing (see above), the way to force it is to return deep.Undefined.

deep.when("hello")
.done(function(s){
	return deep.Undefined;
})
.log();

Anything else...

Any other type : inject it as success in chain (so if the promise was in 'error' state : it's now in 'success' state)

When your callback returns anything else (any other type than those explained here), this value will be seen as "success".

deep.when("hello")
.done(function(s){
	return "world";
})
.log();