Khanh Le
Khanh Le

Reputation: 414

Promise in sequence does not stop when a promise reject

I have a list of promises.

var p1 = {
    run: function () {
        return new Promise(function (resolve, reject) {
            setTimeout(function () {
                resolve("Promise 1 done");
            }, 2000);
        })
    }
};

var p2 = {
    run: function () {
        return new Promise(function (resolve, reject) {
            setTimeout(function () {
                reject("Promise 2 reject");
            }, 1000);
        })
    }
};

var p3 = {
    run: function () {
        return new Promise(function (resolve, reject) {
            setTimeout(function () {
                resolve("Promise 3 done");
            }, 1500);
        })
    }
};

I want to execute [p1,p2,p3] in sequence. I writed a function Process.sequence to act like Promise.all() (resolve when all promises resolve, and reject right after a promise rejects)

Process = {
    sequence: function(promises){
        window.promises = promises;
        return new Promise(function (resolve, reject) {
            promises.reduce(function (sequence, promise) {
                return sequence.then(function () {
                    return promise.run();
                }).then(function (result) {
                    console.log(result);
                    if (promises.indexOf(promise) == promises.length - 1) {
                        resolve("All Done");
                    }
                }).catch(function (reason) {
                    reject(reason);
                });
            }, Promise.resolve());
        });
    }
};

But when i run Process.sequence...

Process.sequence([p1,p2,p3]).then(function(result){
    console.log(result);
}, function(reason){
    console.log(reason);
});

... the p3 still executed even p2 had rejected before.

Here is the result i expect:

Promise 1 done
Promise 2 reject

But this is the real result:

Promise 1 done
Promise 2 reject
Promise 3 done

What wrong with my function Process.sequence?

UPDATE

Thank @JaromandaX for your support. The function Process.sequence should be like this.

Process = {
    sequence: function(promises) {
        return promises.reduce(function(sequence, promise) {
            return sequence.then(function() {
                return promise.run();
            }).then(function(result) {
                console.log(result);
            });
        }, Promise.resolve()).then(function() {
            return "All Done";
        });
    }
};

Upvotes: 0

Views: 1879

Answers (1)

trincot
trincot

Reputation: 351084

As you want the results to contain all of the fulfilled values, and the promises only to be created ("run") when all previous ones were fulfilled, you should make some changes:

  • Make your loop asynchronous, as you can only know whether to continue with the next promise or not when the previous one has resolved.
  • Stop looping when a promise rejects
  • Concatenate the results in an array as you progress

Furthermore, I would not call a variable "promise" when it is not a promise object... that will only bring confusion. Call it task or something. The promise here is what the task.run() method returns.

Here is how I would suggest to do it:

// The p1, p2, p3 objects have been written more concisely using a helper function:
const wait = ms => new Promise(resolve => setTimeout(resolve, ms));

const p1 = { run: _ => wait(2000).then(_ => "Promise 1 fulfilled") };
const p2 = { run: _ => wait(1000).then(_ => { throw "Promise 2 rejected" }) };
const p3 = { run: _ => wait(1500).then(_ => "Promise 3 fulfilled") };

const Process = {
    sequence: function (tasks) {
        return (function loop(results) {
            return results.length >= tasks.length 
                // All promises were fulfilled: return the results via a promise
                ? Promise.resolve(results) 
                // Create the next promise
                : tasks[results.length].run()
                    // When it fulfills, collect the result, and chain the next promise
                    .then(value => loop(results.concat(value)))
                    // When it rejects, return a rejected promise with 
                    // the partial results and the reason of rejection
                    .catch(reason => { throw results.concat(reason) });
        })([]); // Start with an empty results array 
    }
};

console.log('Wait for the results to come in...');
Process.sequence([p1, p2, p3]).then(function(result){
    console.log('Fulfilled: ', result);
}).catch(function(reason){
    console.log('Rejected: ', reason);
});

As browsers have started to support async/await you could also use this more procedural looking code:

const wait = ms => new Promise(resolve => setTimeout(resolve, ms));

const p1 = { run: _ => wait(2000).then(_ => "Promise 1 fulfilled") };
const p2 = { run: _ => wait(1000).then(_ => { throw "Promise 2 rejected" }) };
const p3 = { run: _ => wait(1500).then(_ => "Promise 3 fulfilled") };

const Process = {
    sequence: async function (tasks) {
        const results = [];
        for (let task of tasks) {
            try {
                results.push(await task.run());
            } catch(err) {
                throw results.concat(err);
            }
        }
        return results;
    }
};

console.log('Wait for the results to come in...');
Process.sequence([p1, p2, p3]).then(function(result){
    console.log('Fulfilled: ', result);
}).catch(function(reason){
    console.log('Rejected: ', reason);
});

Upvotes: 2

Related Questions