Skip to content

Programmatic (Dynamic) Chaining and Recursive Functions with JavaScript or Node.js Promises

tuhinpaul edited this page Mar 2, 2018 · 3 revisions

Programmatic (Dynamic) Chaining and Recursive Functions with JavaScript/Node.js Promises

Author Tuhin Paul
Date March 02, 2018
Phone: +1 (306) 880-5494

Table of Contents

1. Some notes on JavaScript Promise construct

2. Programmatic chaining of promises

3. Recursive function using promises

Target Audience: This tutorial is intended for intermediate/advanced JavaScript/Node.js programmers. It's assumed that the reader has a basic understanding of JavaScript promise.

Code examples are explained after the code.

1. JavaScript's Promise Construct

Let's start a simple program with an example of promise usage. You can run the program (filename: promise-naive.js) using this command in the terminal: node promise-naive.js.

// filename: promise-naive.js
new Promise((resolve, reject) => {
    console.log('console output inside promise body.');
    console.log()
    resolve(runAround());
})
.then(result => {
    console.log('Time: ' + new Date().toISOString());
    console.log('Promise result: ' + result);
    console.log()

    // take some time here again:
    runAround();
})
.catch(err => {
    console.log('Time: ' + new Date().toISOString());
    console.log('Error occurred:');
    console.log(err);
    console.log()
});

console.log('Time: ' + new Date().toISOString());
console.log('Exiting program');
console.log()

function runAround() {
    let i = 0;
    for(; i <= .2e9; i++) {
        if (i % 1e8 == 0)
            console.log('i: ' + i);
        let x = i*i;
    }
    console.log()
    return i;
}

The above code produces the following output in my computer:

[tuhin@localhost blog]$ node p.js 
console output inside promise body.

i: 0
i: 100000000
i: 200000000

Time: 2018-02-22T20:43:05.120Z
Exiting program

Time: 2018-02-22T20:43:05.121Z
Promise result: 200000001

i: 0
i: 100000000
i: 200000000

The above program has a chain of two promises. I assume that you can identify them.

Note that the output of the function passed to the then() method was sent to the console after 'Exiting Program' message. This is obvious - due to the asynchronous behaviour promised by promises.

The function passed to the Promise constructor is called an executor. Executor is called immediately. Note that if you comment out the resolve statement, the promise remains in pending state, and therefore, the following chain is not executed because the promises in the chain that follows listen to resolve/reject events.

The function passed to then() is called a handler function. The parameter of the handler function (here, named 'result' in the above example) gets its value from the preceding promise in the chain. When a promise is resolved by calling the resolve() method, the value passed to resolve() is sent down the chain to the next promise.

Important: Note that the then() method returns a pending promise for the purpose of chaining promises. The handler function passed to then() is executed, and the return value of this handler function is used to resolve the pending promise that then() returns. If the handler function throws an error, the projise is rejected with the error that was thrown in the handler function.

Therefore, when you chain promises using then() functions, if a handler function does not exclusively return a promise at the end of a handler function, the handler in the next then() receives undefined. Consider the following code:

new Promise((resolve, reject) => {
    console.log('In Promise constructor.');
    resolve(1);
})
.then(result => {
    console.log('Result from Promise #1: ' + result);
    Promise.resolve(2);
})
.then(result => {
    console.log('Result from Promise #2: ' + result);
    return Promise.resolve(3);
})
.then(result => {
    console.log('Result from Promise #3: ' + result);
})
.catch(err => {
    console.log('Error occurred:');
    console.log(err);
});
console.log('Waiting for promises');

The output of the above code is:

[tuhin@localhost blog]$ node p.js 
In Promise constructor.
Waiting for promises
Result from Promise #1: 1
Result from Promise #2: undefined
Result from Promise #3: 3

Note that in the above code, the handler function of the first then() method did not return anything althoug the last statement was Promise.resolve(). Therefore, the pending promise created by the then() method was resolved with undefined (because of no explicit return). However, notice that returning in this way is not necessary for the executor function in the promise constructor. If you call resolve(), the promise is resolved with the argument of the call to resolve().

2. Programmatic chaining of promises

Now you know how to chain promises. But what if you have to dynamically create the chain? If you have three tasks to perform in promises, you can write code to chain them. But what if you have n tasks that you need to chain? Then you should form the promise chain programmatically.

Here is a naive and simple example to explain how to achieve this: Let's compute 5! (i.e., factorial(5)) using a chain of promises. One will probably never think of using a promise chain to do this; but as mentioned above, this is a naive example of how to apply programmatic promise chain. And this will also help us continue with similar example in the next section that explains how to write a recursive function that returns promise. Here is the code with a set of static promises:

new Promise((resolve, reject) => {
    resolve(5);
})
.then(result => {
    return Promise.resolve(result*4); // 5*4
})
.then(result => {
    return Promise.resolve(result*3); // 5*4*3
})
.then(result => {
    return Promise.resolve(result*2); // 5*4*3*2
})
.then(result => {
    console.log("Factorial(5) is " + result);
})
.catch(err => {
    console.log(err);
});

Ok, that was easy but still done the hard way. Now what if you have to calculate factorial(n)? Let's assume that n is 10:

let n = 10;

// the chain
let chain;

for(let i=n; i>=2; i--) {
    if (! chain) {
        chain = new Promise( (resolve, reject) => {
            resolve(i);
        });
    }
    else
        chain = chain.then(result=>{
            return Promise.resolve(result*i);
        });
}

// avoiding some sanity checks to keep the example simple:
chain.then(result => {
    console.log("Factorial(" + n + ") is " + result);
})
.catch(err => {
    console.log(err);
});

Note that, we did not return anything from the function passed to the promise constructor. However, the functions passed to then() methods have return statements. The reson has been explained in the previous section.

Note the chunk of code at the end of the code block in the above example: here you are handling the final result and any error thrown in the promise chain.

3. Recursive function using promises

In the previous example, we used a for loop to create the chain of promises. Now, let's replace this with a recursive function:

let n = 10;

function factorial(n) {
    /* Terminationg conditions: */
    if (n < 2 && n >=0)
        return Promise.resolve(1);
    else if (n < 0)
        return Promise.reject('invalid number');

    else {
        return factorial(n-1) /* NOTE: recusion */
            .then( resultNminus1 => {
                return Promise.resolve( n * resultNminus1);
            })
            .catch(err => {
                return Promise.reject(err);
        });
    }
}

let chain = factorial(n)
    .then(result => {
        console.log("Factorial(" + n + ") is " + result);
    })
    .catch(err => {
        console.log(err);
    });

Note that in the recursive version, we are actually replacing the loop with the recursion. In the n-th step, we get the result of factorial(n-1) and piggyback to it the promise that will resolve to n*factorial(n-1). Also note that, after calling factorial(n), we are handling the final result in a then() method outside the factorial() function. The error handler should be used here as well because eror may occur anywhere in the chain.