Blog-Archiv

Donnerstag, 8. September 2016

JS Promises

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.

Try Out




Characterizing a Promise

You could use a Promise when following characterization applies to your use-case.

  • A promise is something that will happen in future, and we don't know when, it is asynchronous, we can not retrieve an immediate result from a promise
  • as nobody can know what future brings, the promise could fail
  • a promise fulfills (or fails) when something asynchronous happens, it hangs until then, thus a Promise is "stateful"
  • a promise fulfills (or fails) just once, not several times

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.

Promise Building Blocks

Following is needed to build a Promise:

  • an executor function (promise body), the AJAX call, executed synchronously, initiating something asynchronous
  • a resolve function (fulfill), what should be done when the call succeeds, executed asynchronously
  • a reject function (fail), what should be done when the call fails, executed asynchronously

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.

How to Promise

You need to implement the executor function, and at least one of resolve or reject.

  • The 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.

  • The resolve function normally receives one parameter, being what the executor function passes to it, or what the preceding promise's resolve or reject returned.

  • The 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.

Promise Chaining

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 always need them. It is just a shortcut. And it could happen that they make life harder, not easier.




Keine Kommentare: