Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Add a then method to a function for callbacks

I've seen a few bits of code recently that look like so:

myFunc(args).then(function() { ... });

I find this callback syntax really elegant. My understanding is this is not part of vanilla JS, and I'd like to be able to use it on occasion without depending on particular libraries, so I'm interested in how to implement this myself. So, how does this kind of thing work, and how would you implement it for a function call?

like image 420
Andrew Avatar asked Oct 05 '22 02:10

Andrew


1 Answers

This pattern is called "promises". Its implemented by jQuery and dojo among others, and one approach would be to look at their code and see how they implement it.

The general implementation pattern is creating a function that returns an object which includes a function (then) to pass a pair of function in as callbacks to the previous method which will then be run either on success or failure. MSDN has more about promises in a blog post here

There's a minimalist implementation posted on gitHub here: Promises GIST

function Promise () {
    this._thens = [];
}

Promise.prototype = {

    /* This is the "front end" API. */

    // then(onResolve, onReject): Code waiting for this promise uses the
    // then() method to be notified when the promise is complete. There
    // are two completion callbacks: onReject and onResolve. A more
    // robust promise implementation will also have an onProgress handler.
    then: function (onResolve, onReject) {
        // capture calls to then()
        this._thens.push({ resolve: onResolve, reject: onReject });
    },

    // Some promise implementations also have a cancel() front end API that
    // calls all of the onReject() callbacks (aka a "cancelable promise").
    // cancel: function (reason) {},

    /* This is the "back end" API. */

    // resolve(resolvedValue): The resolve() method is called when a promise
    // is resolved (duh). The resolved value (if any) is passed by the resolver
    // to this method. All waiting onResolve callbacks are called
    // and any future ones are, too, each being passed the resolved value.
    resolve: function (val) { this._complete('resolve', val); },

    // reject(exception): The reject() method is called when a promise cannot
    // be resolved. Typically, you'd pass an exception as the single parameter,
    // but any other argument, including none at all, is acceptable.
    // All waiting and all future onReject callbacks are called when reject()
    // is called and are passed the exception parameter.
    reject: function (ex) { this._complete('reject', ex); },

    // Some promises may have a progress handler. The back end API to signal a
    // progress "event" has a single parameter. The contents of this parameter
    // could be just about anything and is specific to your implementation.
    // progress: function (data) {},

    /* "Private" methods. */

    _complete: function (which, arg) {
        // switch over to sync then()
        this.then = which === 'resolve' ?
            function (resolve, reject) { resolve(arg); } :
            function (resolve, reject) { reject(arg); };
        // disallow multiple calls to resolve or reject
        this.resolve = this.reject = 
            function () { throw new Error('Promise already completed.'); };
        // complete all waiting (async) then()s
        var aThen, i = 0;
        while (aThen = this._thens[i++]) { aThen[which] && aThen[which](arg); }
        delete this._thens;
    }

};

(Note that this is not my code. I looked through it and it looks good as a starting point, but all credit goes to the original author)

like image 65
Ben McCormick Avatar answered Oct 10 '22 04:10

Ben McCormick