Given the following function:
function isGood(number) {
var defer = $q.defer();
$timeout(function() {
if (<some condition on number>) {
defer.resolve();
} else {
defer.reject();
}
}, 100);
return defer.promise;
}
and an array of numbers (e.g. [3, 9, 17, 26, 89]
), I would like to find the first "good" number. I would like to be able to do this:
var arr = [3, 9, 17, 26, 89];
findGoodNumber(arr).then(function(goodNumber) {
console.log('Good number found: ' + goodNumber);
}, function() {
console.log('No good numbers found');
});
Here is one possible recursive version to implement this: DEMO
function findGoodNumber(numbers) {
var defer = $q.defer();
if (numbers.length === 0) {
defer.reject();
} else {
var num = numbers.shift();
isGood(num).then(function() {
defer.resolve(num);
}, function() {
findGoodNumber(numbers).then(defer.resolve, defer.reject)
});
}
return defer.promise;
}
I wonder if there is a better (maybe non-recursive) way?
I wonder if there is a better way?
Yes. Avoid the deferred antipattern!
function isGood(number) {
return $timeout(function() {
if (<some condition on number>) {
return number; // Resolve with the number, simplifies code below
} else {
throw new Error("…");
}
}, 100);
}
function findGoodNumber(numbers) {
if (numbers.length === 0) {
return $q.reject();
} else {
return isGood(numbers.shift()).catch(function() {
return findGoodNumber(numbers);
});
}
}
maybe non-recursive?
You can formulate a loop that chains lots of then
calls, however recursion is absolutely fine here. If you really wanted the loop, it might look like this:
function findGoodNumber(numbers) {
return numbers.reduce(function(previousFinds, num) {
return previousFinds.catch(function() {
return isGood(num);
});
}, $q.reject());
}
This is however less efficient, as it always looks at all numbers
. The "recursive" version will evaluate it lazily, and only do another iteration if the current number was not good.
maybe faster?
You can fire all isGood
checks in parallel, and wait for the first fulfilled to return. Depending on what isGood
actually does and how well that is parallelizable, this might be "better". It potentially does a lot of unnecessary work, though; you may want to use a promise library that supports cancellation.
An example using the Bluebird library, which has a any
helper function dedicated to this task:
function findGoodNumber(numbers) {
return Bluebird.any(numbers.map(isGood))
}