1

First given an array with a list of promises:

var promises = [promise1, promise2, promise3];

I would like to be able to execute all these promises with a predicate. If the first predicate returns false then return immediately giving a result of what has been processed up to that point and cancelling any remaining promises. For example something like:

Promise.all(promises, p => p != false))
  .then(results => console.log(results));

Results should have what has been processed up to the point where the first predicate failed.

The promises should be processed in series not parallel.

Exocomp
  • 1,477
  • 2
  • 20
  • 29
  • 1
    Can you elaborate a bit on what you mean by predicate? When is `p => p != false` invoked with, and with what? Do you mean that if any Promise resolves to `false`, the `Promise.all` terminates immediately? (remember that the promises will go out in parallel by default, if you want to parse results serially, probably better to use a `for` loop with `await`) – CertainPerformance Jan 10 '19 at 22:52
  • Yes that is right if any resolves to false it should return immediately. – Exocomp Jan 10 '19 at 22:53
  • Do you want the all the Promises to be initiated immediately, or do you want to wait until the previous one resolves (and checking that it isn't `false`) before sending out the next? – CertainPerformance Jan 10 '19 at 22:54
  • Actually wait until first is resolve before initiating the next one, I will update my question to reflect that. – Exocomp Jan 10 '19 at 22:55
  • 1
    Ha. That totally changes the answer. Though I rather like what I wrote for your original request. You should just async/await. – Yona Appletree Jan 10 '19 at 23:14

4 Answers4

0

You cannot cancel a promise. You could however just start the next action when the previous was done:

 async function doUntil(generator, predicate) {
   const result = [];
   let curr;
   while(predicate(curr = await generator()))
     result.push(curr);
   return result;
}

// usable as:
const users = await doUntil(
  () => User.getOneAsync(),
  user => user.loggedIn
);

For sure that can easily be adopted to a list of promises, however then those promises that are not part of the result still get executed, there results go into nowhere:

 const promises = [/*...*/];

 const result = await doUntil(
  () => promises.shift(),
  p => p
 );
Jonas Wilms
  • 132,000
  • 20
  • 149
  • 151
0

My solution is very similar to the one promise by Yona, but in the sense that you only return the last returned promise value that satisfies the predicate.

In the code below, I simply constructed a test promise that returns a random number after 500ms. The random number is then passed into a predicate which returns true of the number exceeds 0.5.

An improvement is that we try to catch an event where the promise might fail. If the promise fails then the chain is immediately broken returning the last result, similar to if the promise returns a result that fails the predicate:

// Here we simply set a test promise that returns a random number after 0.5s
function testPromise() {
  return new Promise(resolve => {
    window.setTimeout(() => resolve(Math.random()), 500);
  });
}

// Execute promises in a specified order, evaluate their output by a predicate
async function awaitSequentialPromises(promises, predicate) {
  let result;
  for (const promise of promises) {
    // Try, so that we can catch if a promise fails
    try {
      const _result = await promise();
      console.log(_result);
      
      // Break out of loop if promise returns results that fails predicate
      if (!predicate(_result))
        break;
      else
        result = _result;
        
    } catch(e) {
      // Break out of loop if promise fails
      break;
    }
  }
  console.log(`Final result: ${result}`);
  return result;
}

// Our predicate is simply if the promise returns a random number > 0.5
awaitSequentialPromises([testPromise, testPromise, testPromise], v => v > 0.5);
Terry
  • 63,248
  • 15
  • 96
  • 118
  • I'm not sure your exception handling is desirable. In mine, it acts exactly like Promise.all, where if one promise rejects, the overall promise rejects with that same error. In yours, if a promise rejects, the overall promise is actually _resolved_ with a partial list of results. That seems bad. – Yona Appletree Jan 11 '19 at 01:12
  • You also accept a list of functions, not promises, which is probably closer to what OP wanted. But the name `promises` for a list of promise returning functions is very misleading. – Yona Appletree Jan 11 '19 at 01:17
  • You also eat the exception... whatever error occurs is lost forever, not even logged. Please fix that. – Yona Appletree Jan 11 '19 at 01:26
  • @YonaAppletree There are some cases where you don't want a rejected promise to throw an exception, because it will mess up script execution later. This is one of the use cases where you actually want to swallow a failed promise. – Terry Jan 11 '19 at 08:32
  • So, if you didn't catch the exception, the returned promise from the function would reject, which would allow the calling code to handle the error. As written, _there is no way to know what error occurred_ and _no way to distinguish it from a void returning promise_. I cannot see how this would ever be desirable, at all. – Yona Appletree Jan 16 '19 at 00:44
  • @YonaAppletree This is when opinions come to play. You can still handle the error, just not on the level of the calling function. We just have different approaches to error handling. – Terry Jan 16 '19 at 05:16
  • Unless I'm missing something, it is not possible to recover the error thrown by the inner promise, nor is it possible to discern between an empty array of promises and an array of one promise that fails. I'm sorry, but while it is an opinion, it seems pretty cut-and-dry to me, especially because it deviates from the contract of Promise.all, which seems like a reasonable default. – Yona Appletree Jan 16 '19 at 22:46
-1

Here's a function that should do what you want. You may want to modify it to handle failing predicates differently.

As others have noted, you cannot "cancel" a promise, since it represents something that has already started, generally speaking.

/**
 * Combines an array of promises with a predicate. Works like Promise.all, but checks each result from the given
 * Promise against the supplied predicate, and if the result value from any Promise fails the predicate, the
 * overall promise is immediatly resolved with those values which passed the predicate.
 */
function allWithPredicate(
    promises /*: Array<Promise<T1>, Promise<T2>, ...> */,
    predicate /*: (mostRecentResult, allResults, promiseIndex)*/
) /*: Promise<[T1, T2, ...]> */ {

    // Handle empty input arrays
    if (promises.length === 0)
        return Promise.resolve([]);

    // Create a manual result promise, as none of the built-in promise manipulation functions will do the trick.
    return new Promise(
        (resolve, reject) => {
            // This array will collect the values from the promises. It needs to be the same size as the array of
            // promises.
            const results = new Array(promises.length);

            // We track the number of resolved promises so we know when we're done. We can't use the length of the
            // array above because the last promise might finish first, which would cause the array to be equal in
            // length to the promises array.
            let resolvedCount = 0;

            // Now we go through each promise and set things up
            promises.forEach(
                (promise, index) => {
                    promise.then(
                        result => {
                            // A promise has finished successfully! Hooray. We increment the counter.
                            resolvedCount++;

                            // Now we check if the newly returned value from the promise is acceptable, using the
                            // supplied predicate.
                            if (predicate(result, results, index)) {
                                // If if it, we keep this result.
                                results[index] = result;

                                // If the resolved counter is equal to the original number of promises in the array,
                                // we are done and can return. Note that we do NOT check against the length of
                                // the promises array, as it may have been mutated since we were originally called.
                                if (resolvedCount === results.length) {
                                    resolve(results);
                                }
                            } else {
                                // If not, we short-circuit by resolving the overall promise immediately.
                                // Note that as written, we do NOT include the unacceptable value in the result.
                                resolve(results);
                            }
                        },
                        error => {
                            reject(error);
                        }
                    )
                }
            )
        }
    );
}

Here's a link to a jsbin demo: https://jsbin.com/jasiguredu/edit?js,console

Yona Appletree
  • 8,801
  • 6
  • 35
  • 52
  • "*if the result from any promise fails the predicate, the overall promise is resolved with those values which passed the predicate*" - that doesn't seem to be what the OP wants. – Bergi Jan 11 '19 at 16:19
  • Your function hangs on an empty array of promises. – Bergi Jan 11 '19 at 16:19
  • @Bergi It sure does! I think I originally had a check for empty arrays but it got lost. Fixing. – Yona Appletree Jan 16 '19 at 00:38
-2

Promise.all only takes one argument

var promises = [promise1, promise2, promise3];
Promise.all(promises)
  .then(p => {
      if(p === false) {
          throw new Error(false);
      } else {
          console.log(p);
      }
  })
  .catch(error => { 
      console.log(error);
  });
}

If a promise in Promise.all ends in a catch statement, then it finishes, you can have a counter outside the promise if you need.

Pavlo
  • 1,157
  • 7
  • 13
  • I don't think this is what OP wants... they want something that checks the results of each promise and resolves the outer promise at that point. – Yona Appletree Jan 10 '19 at 23:05