Dynamic Chaining in Javascript Promises

How can I perform dynamic chaining in Javascript Promises, all the time I have seen only hardcoding of the calls for eg., (promise).then(request/functionName).then(request/functionName)

Answers


One option is to utilize the properties of objects and the ability to invoke them via strings.

I wrote a small sample Here and posted it below.

The idea is that you have the set of functions that you wish to run set in some namespace or object, as I did in 'myNamespace':

myNamespace = {
    "A": function() {return "A Function";},
    "B": function() {return "B Function";},
    "C": function() {return "C Function";}
}

Then your main promise would run and somehow (via inputs, ajax, prompts, etc.) you would get the string value of the function you want to have run, which isn't known until runtime:

My main promise uses a prompt to get a letter from the user:

var answer = prompt('Starting.  Please pick a letter: A,B,C');
        if(myNamespace[answer] === undefined)
        {
            alert("Invalid choice!");
            reject("Invalid choice of: " + answer);
        }
        else
        {
            resolve(answer);
        }

In the next 'then' I use that value (passed via the resolve function) to invoke the function:

.then(function(response) {
        funcToRun = myNamespace[response]();})

Finally, I output to html the result of my dynamic function call and I use some recursive fun to make it more interactive and demonstrate that it is dynamic:

.then(function(){
        document.getElementById('result').innerHTML = funcToRun;})
    .then(function(){
        if(prompt("Run Again? (YES/NO)")==="YES")
        {
            doWork();
        }
    });

myNamespace = {
    "A": function() {return "A Function";},
    "B": function() {return "B Function";},
    "C": function() {return "C Function";}
}

function doWork()
{
    var funcToRun;
    
    new Promise(function(resolve,reject) {
        var answer = prompt('Starting.  Please pick a letter: A,B,C');
        if(myNamespace[answer] === undefined)
        {
            alert("Invalid choice!");
            reject("Invalid choice of: " + answer);
        }
        else
        {
            resolve(answer);
        }
    })
    .then(function(response) {
        funcToRun = myNamespace[response]();})
    .then(function(){
        document.getElementById('result').innerHTML = funcToRun;})
    .then(function(){
        if(prompt("Run Again? (YES/NO)")==="YES")
        {
            doWork();
        }
    });
}

doWork();
<div id="result"></div>

Given an array functions that all return promises, you can use reduce() to run them sequentially:

var myAsyncFuncs = [
    function (val) {return Promise.resolve(val + 1);},
    function (val) {return Promise.resolve(val + 2);},
    function (val) {return Promise.resolve(val + 3);},
];

myAsyncFuncs.reduce(function (prev, curr) {
    return prev.then(curr);
}, Promise.resolve(1))
.then(function (result) {
    console.log('RESULT is ' + result);  // prints "RESULT is 7"
});

The example above uses ES6 Promises but all promise libraries have similar features.

Also, creating the array of promise returning functions is usually a good candidate for using map(). For example:

myNewOrmModels.map(function (model) {
    return model.save.bind(model);
}).reduce(function (prev, curr) {
    return prev.then(curr);
}, Promise.resolve())
.then(function (result) {
    console.log('DONE saving');
});

Since promises unwrap, just continue to add then statements and it will continue to be chained together

function asyncSeries(fns) {
  return fns.reduce(function(p, fn) {
    return p.then(fn);
  }, Promise.resolve());
}

Recursively is a pretty cool way to do it as well :)

function countTo(n, sleepTime) {
  return _count(1);

  function _count(current) {
    if (current > n) {
      return Promise.resolve();
    }

    return new Promise(function(resolve, reject) {
      console.info(current);
      setTimeout(function() {
        resolve(_count(current + 1));
      }, sleepTime);
    });
  }
}

This is ES7 way.

Let's say you have multiple promises defined in an array.

  var funcs = [
    _ => new Promise(res => setTimeout(_ => res("1"), 1000)),
    _ => new Promise(res => setTimeout(_ => res("2"), 1000))
  }

And you want to call like this.

 chainPromises(funcs).then(result => console.log(result));

You can use async and await for this purpose.

  async function chainPromises(promises) {
    for (let promise of promises) {  // must be for (.. of ..)
      await promise();
    }
  }

This will execute the given functions sequentially(one by one), not in parallel. The parameter promises is an array of functions, which return Promise.

Plunker: http://plnkr.co/edit/UP0rhD?p=preview


I just had a problem with my api provider that doing Promise.all() would end up in concurrency db problems..

The deal with my situation is that i need to get every promise result in order to show some "all ok" or "some got error" alert.

And i don't know why .. this little piece of code who uses reduce when the promises resolved i couldn't get my scope to work (too late to investigate now)

$scope.processArray = function(array) {
    var results = [];
    return array.reduce(function(p, i) {
        return p.then(function() {
            return i.then(function(data) {
                results.push(data);
                return results;
            })
        });
    }, Promise.resolve());
}

So thanks to this post http://hellote.com/dynamic-promise-chains/ I came with this little bastard.. It's not polished but it's working all right.

$scope.recurse = function(promises, promisesLength, results) {

    if (promisesLength === 1) {
        return promises[0].then(function(data){
            results.push(data);
            return results;
        });
    }

    return promises[promisesLength-1].then(function(data) {
        results.push(data);
        return $scope.recurse(promises, promisesLength - 1, results);
    });

}

Then i invoke the function like this:

var recurseFunction = $scope.recurse(promises, promises.length, results);
recurseFunction.then(function (response) { ... });

I hope it helps.


This solution based on usage promises of introduced in the EcmaScript 6 (https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise), so before use it see table browser`s support https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise#Browser_compatibility

Code

var f1 = function(){
    for (var i = 0; i < 800000000; i++) {}
    console.log('Function1 is done');
}
var f2 = function(){
    for (var i = 0; i < 800000000; i++) {}
    console.log('Function2 is done');
}
var f3 = function(){
    for (var i = 0; i < 800000000; i++) {}
    console.log('Function3 is done');
}
var f4 = function(){
    for (var i = 0; i < 800000000; i++) {}
    console.log('Function4 is done');
}


callbacks = function(){

    // copy passed arguments
    var callbacks = arguments;

    // create array functions
    var callbacks = Object.keys(callbacks).map(function(el){ return callbacks[el] });

    var now = Date.now();

    callbacks.reduce(function(previousPromise, currentFunc){
        return previousPromise.then(
            function(){
                currentFunc();
                var seconds = (Date.now() - now) / 1000;
                console.log('Gone', seconds, 'seconds');
            }
        )
    }, Promise.resolve());
}

callbacks(f1, f2, f3, f4);

Result in Chrome console (values seconds will be different):

Function1 is done
Gone 1.147 seconds
Function2 is done
Gone 2.249 seconds
Function3 is done
Gone 3.35 seconds
Function4 is done
Gone 4.47 seconds

Notes:

  1. It is does not work if a function contains a timer (for this problem I try also jQuery`s $Callbacks, $.Ajax and $.When but it not help. The only decision, what I found, usage resolve() in callback of a timer, but it is not acceptable if you have completed functions.).
  2. Testing environment

$ google-chrome --version
Google Chrome 53.0.2785.116

I think the simplest way is:

const executePromises = function(listOfProviders){

    const p = Promise.resolve(null);

    for(let i = 0; i < listOfProviders.length; i++){
       p = p.then(v => listOfProviders[i]());
    }

   return p;

};

I believe the above is basically equivalent to:

const executePromises = async function(listOfProviders) {

    for(let i = 0; i < listOfProviders.length; i++){
       await listOfProviders[i]();
    }

};

Check the following tutorial for

  1. programmatic (dynamic) chaining of javascript/node.js promises and
  2. Promise chaining using recursive functions

Programmatic-Chaining-and-Recursive-Functions-with-JavaScript-Promise


Need Your Help

elisp regexp search in strings, not buffers

regex string emacs elisp buffer

I have been searching everywhere in the emacs lisp documentation for how to regular expressions search into a string. All I find is how to do this in buffers.

Transparent background on winforms?

c# winforms background transparent

I wanted to make my windows form transparent so removed the borders, controls and everything leaving only the forms box, then I tried to the BackColor and TransparencyKey to transparent but it didn...