Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Break a Bluebird .each() process

Tags:

bluebird

I'm converting from Async to Bluebird and can't figure out how to break a loop

Here's what I'm trying to achieve:

  1. Loop through an array of data.
  2. For each item, check if it exists on DB.
  3. Add one item to the DB (first item that doesn't exist), and exit the .each() loop.

Any help will be highly appreciated.

like image 384
Ronen Teva Avatar asked Oct 18 '25 01:10

Ronen Teva


1 Answers

Bluebird does not have a built in function for that type of operation and it's a little bit difficult to fit into the promise iteration model because iterators return a single value (a promise) which doesn't really give you the opportunity to communicate back both success/error and stop iteration.

Use Rejection to Stop Iteration

You could use Promise.each(), but you'd have to use a coded rejection in order to stop the iteration like this:

var data = [...];

Promise.each(data, function(item, index, length) {
    return checkIfItemExists(item).then(function(exists) {
        if (!exists) {
            return addItemToDb(item).then(function() {
                // successfully added item to DB
                // lets reject now to stop the iteration
                // but reject with a custom signature that can be discerned from an actual error
                throw {code: "success", index: index};
            });
        }
    })
}).then(function() {
    // finished the iteration, but nothing was added to the DB
}, function(err) {
    if (typeof err === "object" && err.code === "success") {
        // success
    } else {
        // some sort of error here
    }
});

This structure could be put into a reusable function/method if you have to use it regularly. You just have to adopt a convention for a rejected promise that really just meant to stop the iteration successfully rather than an actual error.

This does seem like an interesting and not all that uncommon need, but I haven't seen any particular defined structure with Promises for handling this type of issue.


If it feels like overloading a reject as in the above scenario is too much of a hack (which it sort of does), then you could write your own iteration method that uses a resolved value convention to tell the iterator when to stop:

Custom Iteration

Promise.eachStop = function(array, fn) {
    var index = 0;

    return new Promise(function(resolve, reject) {

        function next() {
            if (index < array.length) {
                // chain next promise
                fn(array[index], index, array.length).then(function(result) {
                    if (typeof result === "object" && result.stopIteration === true) {
                        // stopped after processing index item
                        resolve(index);
                    } else {
                        // do next iteration
                        ++index;
                        next();
                    }
                }, reject);
            } else {
                // finished iteration without stopping
                resolve(null);
            }
        }

        // start the iteration
        next();
    });
}

Here if the iterator resolves with a value that is an object has has a property stopIteration: true, then the iterator will stop.

The final promise will reject if there's an error anywhere and will resolve with a value of null if the iterator finished and never stopped or with a number that is the index where the iteration was stopped.

You would use that like this:

Promise.eachStop(data, function(item, index, length) {
    return checkIfItemExists(item).then(function(exists) {
        if (!exists) {
            return addItemToDb(item).then(function() {
                // return special coded object that has stopIteration: true
                // to tell the iteration engine to stop
                return {stopIteration: true};
            });
        }
    })
}).then(function(result) {
    if (result === null) {
        // finished the iteration, but nothing was added to the DB
    } else {
        // added result item to the database and then stopped further processing
    }
}, function(err) {
    // error
});

Flag Variable That Tells Iterator Whether to Skip Its Work

In thinking about this some more, I came up with another way to do this by allowing the Promise.each() iteration to run to completion, but setting a higher scoped variable that tells your iterator when it should skip its work:

var data = [...];
// set property to indicate whether we're done or not
data.done = false;

Promise.each(data, function(item, index, length) {
    if (!data.done) {
        return checkIfItemExists(item).then(function(exists) {
            if (!exists) {
                return addItemToDb(item).then(function() {
                    data.done = true;
                });
            }
        })
    }
}).then(function() {
    // finished
}, function(err) {
    // error
});
like image 110
jfriend00 Avatar answered Oct 19 '25 19:10

jfriend00



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!