Search code examples
javascriptnode.jses6-promise

Node.js Promise chain resolving too early


There's something about chaining promises that I don't understand. The Node.js snippet below produces the following output. Why is promise.allSettled called after the first sleep on line 18 and not after the 2nd one on line 21?

Cycle 0 is going to sleep     promise.js:2
Cycle 0 slept for 2 seconds   promise.js:6
Returned from sleep function  promise.js:19
Cycle 0 is going to sleep     promise.js:2
Done with the process         promise.js:27
Cycle 0 slept for 2 seconds   promise.js:6

function sleep(cycle) {
    console.log(`Cycle ${ cycle } is going to sleep`);

    return new Promise(resolve => {
        setTimeout(() => {
            console.log(`Cycle ${ cycle } slept for 2 seconds`);
            resolve();
        }, 2000);
    });
}

function process() {
    let cycles       = 1;
    let subprocesses = [];

    for (let i = 0; i < cycles; i++) {
        subprocesses.push(
            sleep(i).then(() => {
                console.log('Returned from sleep function');

                sleep(i);
            })
        );
    }

    Promise.allSettled(subprocesses).then(results => {
        console.log('Done with the process');
    });
}

process();


Solution

  • Because you haven't resolved the promise created sleep(i).then to the promise from the second sleep, so it doesn't wait for that second operation to finish before settling. You need a return in front of the second call:

    function sleep(cycle) {
        console.log(`Cycle ${ cycle } is going to sleep`);
    
        return new Promise(resolve => {
            setTimeout(() => {
                console.log(`Cycle ${ cycle } slept for 2 seconds`);
                resolve();
            }, 2000);
        });
    }
    
    function process() {
        let cycles       = 1;
        let subprocesses = [];
    
        for (let i = 0; i < cycles; i++) {
            subprocesses.push(
                sleep(i).then(() => {
                    console.log('Returned from sleep function');
    
                    return sleep(i); // <============================
                })
            );
        }
    
        Promise.allSettled(subprocesses).then(results => {
            console.log('Done with the process');
        });
    }
    
    process();

    Here's a version with two cycles, and with the sleep calls more clearly marked:

    function sleep(cycle, sub) {
        console.log(`Cycle ${cycle}(${sub}) is going to sleep`);
    
        return new Promise(resolve => {
            setTimeout(() => {
                console.log(`Cycle ${cycle}(${sub}) slept for 2 seconds`);
                resolve();
            }, 2000);
        });
    }
    
    function process() {
        let cycles       = 2;
        let subprocesses = [];
    
        for (let i = 0; i < cycles; i++) {
            subprocesses.push(
                sleep(i, "a").then(() => {
                    console.log(`Returned from sleep function (${i})`);
    
                    return sleep(i, "b"); // <============================
                })
            );
        }
    
        Promise.allSettled(subprocesses).then(results => {
            console.log('Done with the process');
        });
    }
    
    process();

    It might be clearer to split that sleep, sleep serial operation off into its own function, perhaps an async function:

    async function twoSleep(i) {
        await sleep(i, "a");
        console.log(`Returned from sleep function (${i})`);
        await sleep(i, "b");
    }
    
    function process() {
        let cycles       = 2;
        let subprocesses = [];
    
        for (let i = 0; i < cycles; i++) {
            subprocesses.push(twoSleep(i));
        }
    
        Promise.allSettled(subprocesses).then(results => {
            console.log('Done with the process');
        });
    }
    

    function sleep(cycle, sub) {
        console.log(`Cycle ${cycle}(${sub}) is going to sleep`);
    
        return new Promise(resolve => {
            setTimeout(() => {
                console.log(`Cycle ${cycle}(${sub}) slept for 2 seconds`);
                resolve();
            }, 2000);
        });
    }
    
    async function twoSleep(i) {
        await sleep(i, "a");
        console.log(`Returned from sleep function (${i})`);
        await sleep(i, "b");
    }
    
    function process() {
        let cycles       = 2;
        let subprocesses = [];
    
        for (let i = 0; i < cycles; i++) {
            subprocesses.push(twoSleep(i));
        }
    
        Promise.allSettled(subprocesses).then(results => {
            console.log('Done with the process');
        });
    }
    
    process();