0

The Javascript function Promise.allSettled waits for all resolved or rejected promises to finish. Promise.all won't return the resolved promises if any of them are rejected, and Promise.race and Promise.any only give the first settled or fufilled promise.

Is there a way to send off multiple promises (say 100), and then as soon as one fails (perhaps have 20 succeed) cancel the unsettled promises (of which there are 100 - 20 - 1 = 79 not yet settled)? I don't even need the details of which failed, but just a sort of Promise.someSettled function.

For context, I'm mapping between data and fetch calls, and I'd like to know what fetches need to be retried.

Is that possible?

9
  • It sounds like you're trying to build a progress indicator for a large number of async calls; it would be good to mention that in your question. Commented Dec 23, 2021 at 17:05
  • 1
    Promises don't have a mechanism to be cancelled. Besides that, what are you trying to build here? Promises might not be the right tool for you. Commented Dec 23, 2021 at 17:08
  • What is supposed to happen if none of the promises fails? Commented Dec 25, 2021 at 11:26
  • 1
    Probably just me but the two statements "don't even need the details of which failed" and "know what fetches need to be retried" appear to be at odds with each other. Commented Dec 25, 2021 at 11:29
  • If the goal is to retry failed fetches, then all you need is to apply a retry pattern to every fetch individually. Reading between the lines, that's what you want but with the additional requirement that retrying ceases when some arbitrary number of fetches (or their retries) have been successful. Commented Dec 25, 2021 at 11:35

4 Answers 4

1

Because the input promises and output resolve values aren't one-to-one, I think the easiest way to manage this would be to just push to an outer array on resolve, and use Promise.all to wait for one rejection, something like:

const makePromises = () => Array.from( { length: 100 }, (_, i) => new Promise((resolve, reject) => { setTimeout(() => { if (Math.random() > 0.05) resolve(i); else reject(); }, 1000 * Math.random()); }) ); const resolveValues = []; Promise.all( makePromises() .map(prom => prom.then(val => { resolveValues.push(val); })) ) .then(() => { console.log('Unexpected - All promises resolved'); }) .catch(() => { const innerResolveValues = [...resolveValues]; console.log('Done:'); console.log(innerResolveValues); });

Sign up to request clarification or add additional context in comments.

1 Comment

Mapping and pushing is really smart.
1

It sounds like you're trying to build a progress indicator for a large number of calls.

A simple solution would be to break the large number of calls into smaller groups and use Promise.allSettled() to determine when each group is finished. You can then compare the number of finished groups to the number of unfinished groups to get a measurement of progress.

1 Comment

That's a good work around for sure.
1

Yes, @Certainperformance hit it on the head! Here is another way of assembling the array of promises but the idea is exactly the same:

for (var acc=[], pr=[], i=0; i<100; i++) pr.push(new Promise((rs,rj)=>setTimeout((i<20?rs:rj).bind(null,i),200*i)) .then(d=>{acc.push(d)})) Promise.all(pr).then( d=>{console.log("all done!", acc)}, e=>{console.log("with some errors:", acc)} );
.as-console-wrapper {max-height: 100% !important; top: 0; }

In my example I let the first finishing promises succeed and reject the later ones (for i>=20). The promises finish in a sequence of 0.2s one after another. So, after 4.2 seconds the first rejected promise is returned and the "failure" branch of the outer .then() will be executed, listing the accumulated successful promises in acc.

Comments

1

Let's add a generator into the mix ;)

That way we don't have to wait for all Promises to resolve or one to throw before we can process the successful items.

const makePromises = () => Array.from({ length: 100 }, (_, i) => new Promise((resolve, reject) => { setTimeout(Math.random() > 0.05 ? resolve : reject, 1000 * Math.random(), i); }) ); // Takes anything iterable as long as it's syncronously iterable // and a finite list. async function* asTheyResolve(promises) { let itemsLeft = 0, deferred; const init = (resolve, reject) => (deferred = { resolve, reject }); // shove my result into the promise that is currently awaited const resolve = (value) => deferred.resolve(value); const reject = (error) => deferred.reject(error); for (let item of promises) { itemsLeft++; Promise.resolve(item).then(resolve, reject); } while (itemsLeft-- > 0) { // creates a new Promise and awaits it. yield await new Promise(init); } } (async () => { try { const start = performance.now(); for await (let value of asTheyResolve(makePromises())) { console.log("time: %f value: %o", performance.now() - start, value); } } catch (error) { console.log("one has thrown", error); } })();

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.