Published: 2016-09-08
Updated: 2016-09-17
Web: http://fritzthecat-blog.blogspot.com/2016/09/js-promises.html
HTML-5 browsers provide a natively implemented JavaScript Promise. That means, you don't need a JS library any more to use Promises, they are now built into the language like String and Date.
Do we need promises? No, we don't. What Promises do has been done by JS programmers for more than 15 years now. It is just a single deferred callback with explicit error handling. Another toy, or a best practice that hopefully will not cause too many abuses and misunderstandings.
I searched the web for a short and concise tutorial about Promise (or Future, or Deferred) that just explains when and how to use it, but I didn't find any. So here is mine.
You could use a Promise when following characterization applies to your use-case.
For example, a Promise could perfectly perform an AJAX call. The processing of something that an HTML page fetches from its server asynchronously.
But a Promise is not a background-thread. Until now we don't have threads in JavaScript.
Following is needed to build a Promise:
Just the resolve
and reject
functions can return a value. This value would be passed to the next Promise, created by the promise's then()
function.
The promise's then()
function should be called with two parameters, both functions, both could be undefined
:
promise.then(resolveFunction, rejectFunction)
The then()
function returns another Promise instance, so that you can chain promises together. (If you can't get enough of them:-)
Mind that the Promise itself is NOT asynchronous, it is the executor
function that initiates something asynchronous. The executor
also must make sure that this asynchronous thing calls the resolveFunction
on success. The Promise will call the rejectFunction
on any exception thrown, but it won't call the resolveFunction
automatically when NO exception happens.
You need to implement the executor
function, and at least one of resolve
or reject
.
executor
function receives two parameters, the first being the resolve
function, the second reject
. These will be the functions passed to promise.then()
. The asynchronous part of the executor
needs to call at least resolve()
. It also can call reject()
explicitly instead of throwing an exception, this is useful when needing more than one reject-parameters. resolve
function normally receives one parameter, being what the executor
function passes to it, or what the preceding promise's resolve
or reject
returned. reject
function normally receives one parameter, being what the executor
function passes to it, or the exception thrown by executor
. The executor
function is passed to the Promise
constructor. That function is executed immediately by the constructor, it is NOT the then()
function which starts it. On the constructed Promise
you must call then()
, passing the concrete implementations for resolve
and reject
as parameters. One of these functions will be called in any case, be it that the asynchronous callback already arrived or not.
In case you get back a Promise from somewhere, call promise.then(resolve, reject)
, and that's it.
Chaining is something like promise.then(a, b).then(c, d).then(e, f)
. I am not sure whether this is really useful, it could easily result in hard-to-read monolithic implementations.
Copy & paste following code into the text-area above and run it. This demonstrates how the return values are passed to the follower-promise.
var makePromise = function(executor, resolve, reject) {
return new Promise(executor).then(resolve, reject);
};
var followerPromise = makePromise(
function(resolve, reject) {
if (confirm("Do you want to keep the promise?"))
resolve("User decided to keep the promise!");
else
reject("Sorry, user decided to NOT keep the promise!");
},
function(resolveParameter) {
alert("I am the resolve function: "+resolveParameter);
return "This resolve goes to the follower promise";
},
function(error) {
alert("I'm the reject function: "+error);
return "This reject goes to the follower promise";
}
);
followerPromise.then(
function(resolveParameter2) {
alert("I'm the second resolve function: "+resolveParameter2);
},
function(error2) {
alert("I'm the second reject function: "+error2);
}
);
In this implementation, both resolve
and reject
return a value. The follower promise is used to process these return values, by giving again resolve
and reject
implementations.
But as we can see when pressing "Cancel", the return of first reject
is passed to second resolve
, not to second reject
- ?
That's the moment where we want to know more about standard behaviour of promises. Being courageous we could trust the A+ specification, but I would recommend to use just the basic features of promises. As I said, we do not need them. It is just sugar. And it could happen that they make life harder, not easier.
ɔ⃝ Fritz Ritzberger, 2016-09-08