Generic Promises

Topics: General
May 15, 2013 at 9:09 AM
I've created a variant of generic Promises for Typescript.

Requires 0.9 alpha and targets ES5.

Nuget is here: http://nuget.org/packages/Promise.TypeScript/
and the Project on Github: https://github.com/pragmatrix/Promise

Heavily inspired by the non-generic version:
https://github.com/stevan/promises-typescript

Feedback is very appreciated. Thanks!
Jun 12, 2013 at 7:52 PM
I can't get the Promise.ts to compile. The compiler does not like the Partial[] type being passed to deferred generic. What "Partial"?
Jun 13, 2013 at 1:32 PM
Edited Jun 13, 2013 at 1:32 PM
Thank you for looking into it. I've updated the project and the nuget package so that it compiles with the 0.9 beta compiler.
Jun 13, 2013 at 5:45 PM
I can't seem to get my head around promises, and what purpose they provide.
The example code shown at https://github.com/stevan/promises-typescript just seems to confuse even more.

I assume it can prevent code from getting closure hell with callbacks.

But to me the following seems to be much easer to understand ->

interface ISimpleCallback { ( cb:()=>void ):void; }
function executeCallbacksInOrder(cbs:ISimpleCallback[],done:()=>void) {
    var l=0;
    var ll=cbs.length;
    function doNextCallback() {
        if(l>=ll) {
            done();
            return;
        }
        var tl=l;l++;
        if(cbs[tl]) cbs[tl](doNextCallback);
        else doNextCallback();
    }
    doNextCallback();
}


var thingsToDo:ISimpleCallback[] = [
    (cb)=>{
        console.log('Our first callback..');
        cb();
    },
    (cb)=>{
        console.log('Our second callback, that could take some time to complete');
        //lets fake a long time.. ok, 1 second.. :)
        setTimeout(()=>{ console.log('Not too long eh!!');cb(); },1000);
    },
    (cb)=>{
        console.log('Our last callback, all done now..');
        cb();
    }
];

executeCallbacksInOrder(thingsToDo,
  ()=>{console.log('All callbacks complete');}
);
Of course it also very simple to create a an executeCallbackTogether function, and also implement error handling.
ps, also found an error with the current 0.8.3.0..
If I try and place the callback directly into the function the compiler crashes..
eg.
executeCallbacksInOrder(
    [
        (cb)=>{
            console.log('Our first callback..');
            cb();
        },
        (cb)=>{
            console.log('Our second callback, that could take some time to complete');
            //lets fake a long time.. ok, 1 second.. :)
            setTimeout(()=>{ console.log('Not too long eh!!');cb(); },1000);
        },
        (cb)=>{
            console.log('Our last callback, all done now..');
            cb();
        }
    ],
    ()=>{console.log('All callbacks complete');}
);
Jun 16, 2013 at 10:49 AM
A promise is not really about executing callbacks. The word "Promise" in English means "to give an assurance that something will be done".

The thing that "will be done" in the context of a JavaScript promise is normally to return a value. In short a Promise undertakes to return a value computed through an arbitrarily complex operation (or report back an error) when a caller requests that value:
 var promise = new Promise();
 
 ...
 // Caller requests value
 promise.then(value => /* I promise to return this value*/);
Now this might look like a callback on the face of it, but in fact the Promise is a statefull object with well defined behaviour (see the Promise A+ spec for a good explanation); for example one of the rules of Promises is that "then" may be called multiple times, but must return the same result every time. Essentially this permits one to treat a Promise as an object with an immutable value.

Consider:

 function foo(value:number){
    console.log(value);
    console.log(value+1);
}

 function bar(promise: Promise){
    promise.then( value =>   console.log(value));
    promise.then( value =>   console.log(value+1));
 }  

 var primitive = 10;
 var promise = new Promise();
 promise.resolve(10); 
 
  foo(primitive);  // logs 10, 11
  bar(promise);  // logs 10, 11
  
The point is the value from the promise can be the result of an arbitrarily complex sequence of events, but we can still treat it very much like a primitive.
Jan 15, 2014 at 1:59 AM
Edited Jan 15, 2014 at 1:59 AM
Not working for TypeScript 0.9.5.
Jan 16, 2014 at 5:55 AM
Promises do nothing but offer cute semantics around callbacks.

The problem with callbacks and async code in general is readability. Subtle bugs creep in when you read a method from top to bottom and it executes in a much different order.

I really like KpjComp's style of handling async flows. It's more like a state machine, and the order you read things is the order in which they are executed. I think web developers need to understand and embrace this style. Treat callbacks as dependencies. "Callback hell" is purely a self-made nightmare (and I've been there).

With that aside, I like your lib, pragmatix. It's nice with TypeScript's beautiful "fat arrow" syntax.
Jan 16, 2014 at 9:06 AM
This code:
var thingsToDo:ISimpleCallback[] = [
    (cb)=>{
        console.log('Our first callback..');
        cb();
    },
    (cb)=>{
        console.log('Our second callback, that could take some time to complete');
        //lets fake a long time.. ok, 1 second.. :)
        setTimeout(()=>{ console.log('Not too long eh!!');cb(); },1000);
    },
    (cb)=>{
        console.log('Our last callback, all done now..');
        cb();
    }
];
can be represented using those promises:
// helper function
function wait(millis) {
  return new Promise(resolve => setTimeout(resolve, millis));
}

Promise.resolve(console.log('Our first callback..'))
.then(() => wait(1000))
.then(() => console.log('Not too long eh'))
.then(() => console.log('our last callback'));
I don't know but for me the second version is more readable and less indented :) Moreover the Promise object is slowly introduced in browsers: http://caniuse.com/promises so in Chrome I don't need any custom code to run it.

Promises are good in resolving data-flow like problems when usually you end-up with pyramid of callbacks:
importDataSet('myFile.csv',function () {
  importDataSet('myFile.csv',function () {
    DataSet.find({title: 1}, function (err, result) {
        result.length.should.be.equal(2);
        result[0].title.should.startWith('myFile');
        result[1].title.should.startWith('myFile');
        result[0].title.should.not.be.equal(result[0].title);
        done();
      });
    });
  });
  done();
}); 
After rewriting you get this:
importDataSet('myFile.csv')
  .then(function () {
  return importDataSet('myFile.csv')
}).then(function () {
  return DataSet.find({title: 1})
}).then(function (result) {
  result.length.should.be.equal(2);
  result[0].title.should.startWith('myFile');
  result[1].title.should.startWith('myFile');
  result[0].title.should.not.be.equal(result[0].title);
  done();
});
Taken from: https://stackoverflow.com/questions/20641074/how-to-refactor-a-callback-pyramid-into-promise-based-version

Promises have several advantages over KpjComp simple solution:
  1. result from one promise is passed to next one
  2. if a promise returns another promise it's added to the chain, in the callback version you would create another step in callback pyramid passing cb along (the setTimeout case)
  3. you get automatic error propagation as promises have "catch" method
I would argue that promises promote more "functional-like programming" as you tend to write small functions that just work on the arguments and not callbacks with ever growing function scope. Those callbacks can then access variables of any function in scope increasing the number of function dependencies (hidden arguments, like globals).

Note that Promises as outlined in the A+ spec also have drawbacks or rather things to keep in mind. This point http://promises-aplus.github.io/promises-spec/#point-39 tells to execute then actions with a fresh stack - Promise polyfills do it with setTimeout/requestAnimationFrame and other tricks and that will always be slower than direct callback. Native Promises in Chrome are as fast as callbacks (from my personal experience) but the "fresh stack" requirement can also bite you in cases when you need to be in the same stack. Those are rare but for example when working with IndexedDB transactions the requests (read/write) need to be placed in the same stack or the transaction completes.

I recommend reading this excellent article: http://www.html5rocks.com/en/tutorials/es6/promises/
Jan 16, 2014 at 11:14 AM
@jackjoy I've created an issue on github for that: https://github.com/pragmatrix/Promise/issues/3
@jscharf thanks :)