Slava Fomin II Slava Fomin II - 1 year ago 56
Node.js Question

Sequentially iterate list of asynchronous functions until at least one returns truthy value in Node.js

Given an array containing references to asynchronous functions (which return promises or simple values), I want to sequentially iterate over it resolving each element, until at least one of the elements resolves to the truthy value.

The idea came from this simple synchronous code:

var hasAccess = (user.isAdmin() || user.isManager() || entity.isOwnedBy(user));

The code above works for synchronous functions well, but will break for asynchronous ones. I'm looking for an asynchronous replacement of the code above.

I expect it to be called like this (in ES6 syntax for brevity):

() => user.isAdmin(), // Called first
() => user.isManager(), // Called second if first returned falsy value
() => entity.isOwnedBy(user) // Called third if all previous returned falsy values
]).then(function (result) {
// result === (true || false)
}).catch(function () {
// Promise chain is rejected when at least one element in rejected

All the functions in the example above return either promises or simple values directly.

Is there an out-of-the box solution for this? I'm using Bluebird promises, however, I couldn't find anything suitable in it's API. I can't use e.g.
, because it only checks for resolved promises and doesn't check returned values. I can't use
either, because it will execute all functions, but I wan't them to be executed only when necessary (i.e. when previous element failed).

A node module recommendation would be great (I couldn't find one), however, a concept of how
could be coded would also be nice because I have some doubts about its implementation.

Answer Source

Because a then handler can return a promise, you can just make a recursive solution.

function atLeastOneTruthy(arrayOfCallables) {
  // Keep a numeric index to avoid mutating the input array.
  // This implementation is not safe for item removal, but appending is OK.
  let nextCallableIndex = 0;
  // Start with a false value as a base case. Use a "named function expression"
  // to allow for recursion.
  return Promise.resolve(false).then(function innerHandler(returnValue) {
    if (returnValue) {
      // If truthy, return immediately. Nothing else is evaluated.
      return returnValue;
    } else if (nextCallableIndex < arrayOfCallables.length) {
      // If falsy, get the next iterable...
      let nextCallable = arrayOfCallables[nextCallableIndex++];
      // ...then call it and try again, regardless of whether nextCallable
      // returns a simple value or a promise.
      return Promise.resolve(nextCallable()).then(innerHandler);
    } else {
      // We're out of callables, and the last one returned a falsy value.
      // Return false. (You could also return returnValue to parallel ||.)
      return false;

See also: Named function expressions. You could also refactor to use a simple nested function.

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download