Promises/A+ implementation in Sciter2

The Promises, as a concept, is generalization of callback mechanism. This pattern is quite popular these days so Sciter2 SDK contains now (sdk/samples/+promise/) pretty simple (60 lines of code) implementation of the Promises.

The promise is an object that:

  1. maintins list/chain of callback function pairs [onsuccess:function, onfailure:function] by providing .then(onsuccess, onfailure) method;
  2. promise object provides the way to "execute" the chain, either succes or failure callbacks (if an error occurs);
  3. each callback function in the chain receives input (parameters) from output (return [values]) of previous callback in the chain.

To create the promise in Sciter simply do this:

var oath = promise();

The promise() function and promise object

The promise() function in my implementation returns function/object that has .then() method defined on it. So to attach callback functions to the promise you will do this:

oath.then( function( data ) { return [data+1] } ) // #1
    .then( function( data ) { return [data+2] } ) // #2
    .then( function( data ) { stdout.println("success:", data)}, // #3 
           function( reason ) { stderr.println("error:", reason)} );

Now we have promise in variable oath that has three onsuccess functions assigned to it.

When time comes for the promise to be fulfilled, our code will do it by invoking the promise (as it is a function) with its first parameter set to true and with additional parameters that will be passed to the first callback in the chain:

oath(true, 1);

This will call first callback with 1 in data. It will return 1 + 1 -> 2.
That 2 value will be passed to second callback that will return 2 + 2 -> 4.
And finally last callback will just do println:

success:4

To reject the promise we just need to call it with first parameter set to false:

oath(false, "something went wrong!");

This will call our sole onerror callback and we will get:

error: something went wrong!

The promise.when() function, parallel execution

The promise has also defined static function promise.when(...)  that accepts list of promises and return another promise that will be fullfilled/rejected when all input promises will be completed.

function printBandC(b,c) { stdout.println(b,c) }

var BandC = 
    promise.when( self.request(#get-json, urlB),
                  self.request(#get-json, urlC)).then(printBandC);

There are quite many articles about the subject, just google for “Promises JavaScript”

Here is the full source of promise.tis module:

//|   
//| Implementation of Promises/A+ specification: http://promises-aplus.github.io/promises-spec/
//| 

function promise() 
{
  var state = null;    // null = pending, true = fulfilled, false = rejected
  var deferred = [];   // functions to call when _promise() is invoked
  var values = [];     // an array of values as arguments for the then() handlers

  function _promise(newState, newValues) 
  {
    if (state === null) {
      state = newState;
      values = newValues;
      self.post( function() { for (var d in deferred) d() } );
    }
  }
  _promise.then = function(onFulfilled, onRejected = null) 
  {
    var newPromise = promise();
    function notify() 
    {
      try {
        var f = state ? onFulfilled : onRejected;
        if (typeof f == #function) {
          var r = f.apply(null, values);
          if (r && typeof r.then == #function) // looks like it is a promise to, chain them
            r.then( function(args..){ newPromise(true,args) }, 
                    function(args..){ newPromise(false,args) } );
          else
            newPromise(true, [r]);
        }
        else
          newPromise(state, values);
      }
      catch (e) {
        newPromise(false, [e]);
      }
    }
    if (state !== null) 
      self.post(notify); // already fullfilled/failed
    else
      deferred.push(notify);      
    return newPromise;
  };
  return _promise;
}

promise.when = function (args..) 
{
  if(args.length == 1) return args[0];

  var oath = promise();
  var n = args.length;
  var res = [];
  function done(i) { return function(v) { res[i] = v; if(--n == 0) oath(true,res); } }
  function fail(v) { oath(false,v) }
  for(var (i,ip) in args) 
      ip.then(done(i), fail);
  return oath;
};

// This makes first parameter of Element.request optional: 
// Element.request( [callback: function | integer,] 
//                  #get | #post | #post-data | #put-data | #post-json | #put-json | #delete, 
//                  url: string 
//                  [, params: object [, headers: object] ] ) : Object | Stream | Bytes | Error
// If callback is omitted the method returns promise: 

(function(){
  var ElementRequest = Element.request;
  // redefining Element.request function
  Element.request = function(args..) {
    if(typeof args[0] == #function || typeof args[0] == #integer)
      return ElementRequest.apply(this,args); // standard call
    // call without callback, send request and return the promise
    var oath = promise();
    ElementRequest.apply(this, function(data,status) 
                              { oath(status == 200, [data,status]); }, args );
    return oath;
  }
})();
  

Leave a Reply

Your email address will not be published. Required fields are marked *

11 + nineteen =