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:
.each() loop.Any help will be highly appreciated.
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
});
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With