Search code examples
javascriptes6-promise

Implement Promise.chain([...]) in Javascript?


Short questions: Why is there no Promise.chain in Javascript (comparable to Promise.all)? Is my implementation o.k.?

My 'codec' behaved wrong:

  1. Reading a graph from a XML file
  2. Creating all nodes (the creation method returns a promise)
  3. Waiting for all node creations to finish
  4. Create all edges between the nodes

The problem: The order of the database calls for the node creation (Step 2) got mixed up at execution time.

Solution: I had to chain the database calls in correct order before the methods became executed.

/**
 * chains a list of functions (that return promises) and executes them in the right order
 * [function() {return Promise.resolve();}, function() {return Promise.resolve();}]
 *
 * @param funcs is an array of functions returning promises
 * @returns {Promise}
 */
function chain_promises(funcs) {
    if (funcs.length < 1) {
        return Promise.resolve();
    }
    var i = 0;
    return chain_executor(funcs, i);
}

/**
 * Recursive help method for chain_promises
 * 1) executes a function that returns a promise (no params allowed)
 * 2) chains itself to the success resolve of the promise
 *
 * @param funcs is an array of functions returning promises
 * @param i is the current working index
 */
function chain_executor(funcs, i) {
    var promise = funcs[i]();
    return promise.then(function(){
        console.log(i);
        if (funcs.length > i+1) {
            return chain_executor(funcs, i+1);
        } else {
            return Promise.resolve();
        }
    })
}

Solution

  • Using Array#reduce you can create this function

    const chain_promises = arrayOfFn => arrayOfFn.reduce((promise, fn) => 
        promise.then(results => 
            fn().then(result => 
                results.concat(result)
            )
        ), Promise.resolve([])
    );
    

    or if you're into one-liners

    const chain_promises = arrayOfFn => arrayOfFn.reduce((promise, fn) => promise.then(results => fn().then(result => results.concat(result))), Promise.resolve([]));
    

    These have the added benefit that the resolved values are all available in the .then

    e.g.

    const chain_promises = arrayOfFn => arrayOfFn.reduce((promise, fn) => 
        promise.then(results => 
            fn().then(result => 
                results.concat(result)
            )
        ), Promise.resolve([])
    );
    
    const wait_promise = (time, result) => new Promise(resolve => setTimeout(resolve, time, result));
    var funcs = [ 
        () => wait_promise(300, 'p1').then(value => ({value, date: new Date()})),
        () => wait_promise(400, 'p2').then(value => ({value, date: new Date()})),
        () => wait_promise(100, 'p3').then(value => ({value, date: new Date()}))
    ];
    const start = new Date();
    chain_promises(funcs)
    .then(results => 
        results.reduce((a, b) => {
            console.log(b.value, b.date - a);
            return b.date;
        }, start)
    );

    Also, passing an empty array to this function won't break - you'll end up with an empty array as the resolved value