Warten Sie, bis alle Versprechen erfüllt sind, auch wenn einige abgelehnt wurden

405

Nehmen wir an, ich habe eine Reihe von Promise s, die Netzwerkanforderungen stellen, von denen eine fehlschlagen wird:

// http://does-not-exist will throw a TypeError
var arr = [ fetch('index.html'), fetch('http://does-not-exist') ]

Promise.all(arr)
  .then(res => console.log('success', res))
  .catch(err => console.log('error', err)) // This is executed   

Nehmen wir an, ich möchte warten, bis alle abgeschlossen sind, unabhängig davon, ob einer fehlgeschlagen ist. Möglicherweise liegt ein Netzwerkfehler für eine Ressource vor, ohne die ich leben kann. Wenn ich sie jedoch erhalten kann, möchte ich, bevor ich fortfahre. Ich möchte Netzwerkfehler ordnungsgemäß behandeln.

Da Promises.allkeinen Raum dafür lassen, was ist die empfohlene Muster diese für die Handhabung, ohne Versprechen Bibliothek?

Nathan Hagen
quelle
Was sollte im resultierenden Array für abgelehnte Versprechen zurückgegeben werden?
Kuba Wyrostek
9
ES6-Versprechen unterstützen keine solche Methode (und sind derzeit anscheinend langsamer als Bluebird ). Darüber hinaus unterstützen sie noch nicht alle Browser oder Engines. Ich würde dringend empfehlen, Bluebird zu verwenden, das allSettledIhren Anforderungen entspricht, ohne dass Sie Ihre eigenen rollen müssen.
Dan Pantry
@KubaWyrostek Ich denke, Sie sprechen den Grund an, warum Promise.all dieses Verhalten nicht hat, was ich für sinnvoll halte. So funktioniert das nicht, aber eine alternative Sichtweise wäre, zu sagen, dass Promise.all ein spezielles Versprechen zurückgeben sollte, das niemals fehlschlägt - und Sie würden den Fehler erhalten, der als Argument für das fehlgeschlagene Versprechen ausgegeben wurde.
Nathan Hagen
Um das zu ergänzen, was Dan geteilt hat, kann die allSettled / settAll-ähnliche Funktionalität von Bluebird über die Funktion "Reflect" verwendet werden.
user3344977
2
@Coli: Hmm, das glaube ich nicht. Promise.allwird abgelehnt, sobald ein Versprechen abgelehnt wird , sodass Ihre vorgeschlagene Redewendung nicht garantiert, dass alle Versprechen erfüllt werden.
Jörg W Mittag

Antworten:

309

Update, Sie möchten wahrscheinlich das integrierte native verwenden Promise.allSettled:

Promise.allSettled([promise]).then(([result]) => {
   //reach here regardless
   // {status: "fulfilled", value: 33}
});

Als lustige Tatsache war diese Antwort unten Stand der Technik beim Hinzufügen dieser Methode zur Sprache:]


Klar, du brauchst nur ein reflect:

const reflect = p => p.then(v => ({v, status: "fulfilled" }),
                            e => ({e, status: "rejected" }));

reflect(promise).then((v => {
    console.log(v.status);
});

Oder mit ES5:

function reflect(promise){
    return promise.then(function(v){ return {v:v, status: "fulfilled" }},
                        function(e){ return {e:e, status: "rejected" }});
}


reflect(promise).then(function(v){
    console.log(v.status);
});

Oder in Ihrem Beispiel:

var arr = [ fetch('index.html'), fetch('http://does-not-exist') ]

Promise.all(arr.map(reflect)).then(function(results){
    var success = results.filter(x => x.status === "fulfilled");
});
Benjamin Gruenbaum
quelle
3
Ich denke, das ist eine großartige Lösung. Können Sie es ändern, um eine einfachere Syntax aufzunehmen? Der Kern des Problems besteht darin, dass Sie Fehler in Teilversprechen behandeln und den Fehler zurückgeben sollten, wenn Sie sie behandeln möchten. So zum Beispiel: gist.github.com/nhagen/a1d36b39977822c224b8
Nathan Hagen
3
Mit @NathanHagen können Sie herausfinden, was abgelehnt und was erfüllt wurde, und das Problem an einen wiederverwendbaren Operator weiterleiten.
Benjamin Gruenbaum
4
Als Antwort auf mein eigenes Problem habe ich das folgende npm-Paket erstellt: github.com/Bucabug/promise-reflect npmjs.com/package/promise-reflect
SamF
2
Ich lief in dieser Frage vor einer Weile und ich schuf dieses NPM - Paket für es: npmjs.com/package/promise-all-soft-fail
velocity_distance
5
Ist das Wort reflectein allgemeines Wort in der Informatik? Kannst du bitte verlinken, wo dies wie auf Wikipedia erklärt wird oder so. Ich suchte intensiv Promise.all not even first reject, wusste aber nicht, ob ich nach "Reflect" suchen sollte. Sollte ES6 eine haben, Promise.reflectdie wie "Promise.all aber wirklich alle" ist?
Noitidart
253

Ähnliche Antwort, aber vielleicht idiomatischer für ES6:

const a = Promise.resolve(1);
const b = Promise.reject(new Error(2));
const c = Promise.resolve(3);

Promise.all([a, b, c].map(p => p.catch(e => e)))
  .then(results => console.log(results)) // 1,Error: 2,3
  .catch(e => console.log(e));


const console = { log: msg => div.innerHTML += msg + "<br>"};
<div id="div"></div>

Je nach Art (en) der Wert zurückgegeben, können Fehler oft leicht genug unterschieden werden (zB Verwendung undefinedfür „do not care“, typeoffür Normal Nicht-Objektwert, result.message, result.toString().startsWith("Error:")etc.)

Ausleger
quelle
1
@ KarlBateman Ich denke du bist verwirrt. Die Reihenfolge der Funktionen, die aufgelöst oder abgelehnt werden, spielt hier keine Rolle, da der .map(p => p.catch(e => e))Teil alle Ablehnungen in aufgelöste Werte Promise.allumwandelt. Warten Sie also immer noch , bis alles abgeschlossen ist, ob einzelne Funktionen aufgelöst oder abgelehnt werden, unabhängig davon, wie lange sie dauern. Versuch es.
Fock
39
.catch(e => console.log(e));wird nie genannt, weil dies nie fehlschlägt
fregante
4
@ bfred.it Das stimmt. Obwohl das Beenden von Versprechensketten mit IMHO imcatch Allgemeinen eine gute Praxis ist .
Fock
2
@SuhailGupta Es fängt den Fehler ab eund gibt ihn als regulären (Erfolgs-) Wert zurück. Gleich wie p.catch(function(e) { return e; })nur kürzer. returnist implizit.
Fock
1
@ JustinReusnow bereits in Kommentaren behandelt. Es empfiehlt sich immer, Ketten zu beenden, falls Sie später Code hinzufügen.
Fock
71

Benjamins Antwort bietet eine großartige Abstraktion zur Lösung dieses Problems, aber ich hatte auf eine weniger abstrahierte Lösung gehofft. Die explizite Möglichkeit, dieses Problem zu beheben, besteht darin, einfach .catchdie internen Versprechen aufzurufen und den Fehler von ihrem Rückruf zurückzugeben.

let a = new Promise((res, rej) => res('Resolved!')),
    b = new Promise((res, rej) => rej('Rejected!')),
    c = a.catch(e => { console.log('"a" failed.'); return e; }),
    d = b.catch(e => { console.log('"b" failed.'); return e; });

Promise.all([c, d])
  .then(result => console.log('Then', result)) // Then ["Resolved!", "Rejected!"]
  .catch(err => console.log('Catch', err));

Promise.all([a.catch(e => e), b.catch(e => e)])
  .then(result => console.log('Then', result)) // Then ["Resolved!", "Rejected!"]
  .catch(err => console.log('Catch', err));

Wenn Sie noch einen Schritt weiter gehen, können Sie einen generischen Catch-Handler schreiben, der folgendermaßen aussieht:

const catchHandler = error => ({ payload: error, resolved: false });

dann kannst du tun

> Promise.all([a, b].map(promise => promise.catch(catchHandler))
    .then(results => console.log(results))
    .catch(() => console.log('Promise.all failed'))
< [ 'Resolved!',  { payload: Promise, resolved: false } ]

Das Problem dabei ist, dass die abgefangenen Werte eine andere Schnittstelle haben als die nicht abgefangenen Werte. Um dies zu bereinigen, können Sie Folgendes tun:

const successHandler = result => ({ payload: result, resolved: true });

Jetzt können Sie Folgendes tun:

> Promise.all([a, b].map(result => result.then(successHandler).catch(catchHandler))
    .then(results => console.log(results.filter(result => result.resolved))
    .catch(() => console.log('Promise.all failed'))
< [ 'Resolved!' ]

Dann, um es trocken zu halten, kommen Sie zu Benjamins Antwort:

const reflect = promise => promise
  .then(successHandler)
  .catch(catchHander)

wo es jetzt aussieht

> Promise.all([a, b].map(result => result.then(successHandler).catch(catchHandler))
    .then(results => console.log(results.filter(result => result.resolved))
    .catch(() => console.log('Promise.all failed'))
< [ 'Resolved!' ]

Die Vorteile der zweiten Lösung sind, dass sie abstrahiert und trocken ist. Der Nachteil ist, dass Sie mehr Code haben und daran denken müssen, alle Ihre Versprechen zu reflektieren, um die Dinge konsistent zu machen.

Ich würde meine Lösung als explizit und KISS charakterisieren, aber in der Tat weniger robust. Die Benutzeroberfläche garantiert nicht, dass Sie genau wissen, ob das Versprechen erfolgreich war oder fehlgeschlagen ist.

Zum Beispiel könnten Sie dies haben:

const a = Promise.resolve(new Error('Not beaking, just bad'));
const b = Promise.reject(new Error('This actually didnt work'));

Dies wird nicht durch erwischt werden a.catch, so

> Promise.all([a, b].map(promise => promise.catch(e => e))
    .then(results => console.log(results))
< [ Error, Error ]

Es gibt keine Möglichkeit zu sagen, welches tödlich war und welches nicht. Wenn dies wichtig ist, sollten Sie eine Schnittstelle erzwingen, die nachverfolgt, ob sie erfolgreich war oder nicht (was der reflectFall ist).

Wenn Sie Fehler nur ordnungsgemäß behandeln möchten, können Sie Fehler einfach als undefinierte Werte behandeln:

> Promise.all([a.catch(() => undefined), b.catch(() => undefined)])
    .then((results) => console.log('Known values: ', results.filter(x => typeof x !== 'undefined')))
< [ 'Resolved!' ]

In meinem Fall muss ich den Fehler nicht kennen oder wissen, wie er fehlgeschlagen ist - es ist mir nur wichtig, ob ich den Wert habe oder nicht. Ich lasse die Funktion, die das Versprechen generiert, sich um die Protokollierung des spezifischen Fehlers kümmern.

const apiMethod = () => fetch()
  .catch(error => {
    console.log(error.message);
    throw error;
  });

Auf diese Weise kann der Rest der Anwendung den Fehler ignorieren, wenn er möchte, und ihn als undefinierten Wert behandeln, wenn er möchte.

Ich möchte, dass meine Funktionen auf hoher Ebene sicher ausfallen und mich nicht um die Details kümmern, warum ihre Abhängigkeiten fehlgeschlagen sind, und ich bevorzuge KISS gegenüber DRY, wenn ich diesen Kompromiss eingehen muss - weshalb ich mich letztendlich für die Nichtverwendung entschieden habe reflect.

Nathan Hagen
quelle
1
@ Benjamin Ich denke, @ Nathans Lösung ist sehr einfach und idiomatisch für Promises. Während Sie reflectdie Wiederverwendung von Code verbessern, wird auch eine andere Abstraktionsebene eingerichtet. Da Nathans Antwort bisher nur einen Bruchteil der Stimmen im Vergleich zu Ihrer erhalten hat, frage ich mich, ob dies ein Hinweis auf ein Problem mit seiner Lösung ist, das ich noch nicht erkannt habe.
2
@ LUH3417 Diese Lösung ist konzeptionell weniger solide, da sie Fehler als Werte behandelt und Fehler nicht von Nichtfehlern trennt. Wenn sich zum Beispiel eines der Versprechen zu Recht auf einen Wert auflöst, der geworfen werden kann (was durchaus möglich ist), bricht dies ziemlich schlecht.
Benjamin Gruenbaum
2
@BenjaminGruenbaum Also wäre zum Beispiel new Promise((res, rej) => res(new Error('Legitimate error'))nicht zu unterscheiden von new Promise(((res, rej) => rej(new Error('Illegitimate error'))? Oder könnten Sie nicht nach filtern x.status? Ich werde diesen Punkt zu meiner Antwort hinzufügen, damit der Unterschied klarer wird
Nathan Hagen
3
Der Grund, warum dies eine schlechte Idee ist, liegt darin, dass die Promise-Implementierung an einen bestimmten Anwendungsfall gebunden ist, der immer nur in einer bestimmten Promise.all()Variante verwendet wird. Außerdem muss der Promise-Verbraucher wissen, dass ein bestimmtes Versprechen nicht abgelehnt wird, sondern wird schluck es ist fehler. Tatsächlich könnte die reflect()Methode durch das Aufrufen weniger "abstrakt" und expliziter gemacht werden. PromiseEvery(promises).then(...)Die Komplexität der obigen Antwort im Vergleich zu Benjamins sollte viel über diese Lösung aussagen.
Neil
33

In Vanille-Javascript gibt es einen fertigen Vorschlag für eine Funktion, die dies nativ erfüllen kann: Diese Promise.allSettledhat es in Stufe 4 geschafft, ist in ES2020 offiziell und in allen modernen Umgebungen implementiert . Es ist der reflectFunktion in dieser anderen Antwort sehr ähnlich . Hier ist ein Beispiel von der Vorschlagseite. Vorher hätten Sie tun müssen:

function reflect(promise) {
  return promise.then(
    (v) => {
      return { status: 'fulfilled', value: v };
    },
    (error) => {
      return { status: 'rejected', reason: error };
    }
  );
}

const promises = [ fetch('index.html'), fetch('https://does-not-exist/') ];
const results = await Promise.all(promises.map(reflect));
const successfulPromises = results.filter(p => p.status === 'fulfilled');

Wenn Sie Promise.allSettledstattdessen verwenden, entspricht das oben Gesagte:

const promises = [ fetch('index.html'), fetch('https://does-not-exist/') ];
const results = await Promise.allSettled(promises);
const successfulPromises = results.filter(p => p.status === 'fulfilled');

Benutzer moderner Umgebungen können diese Methode ohne Bibliotheken verwenden . In diesen sollte das folgende Snippet ohne Probleme ausgeführt werden:

Promise.allSettled([
  Promise.resolve('a'),
  Promise.reject('b')
])
  .then(console.log);

Ausgabe:

[
  {
    "status": "fulfilled",
    "value": "a"
  },
  {
    "status": "rejected",
    "reason": "b"
  }
]

Für älteren Browser gibt es einen spezifikationsgemäßen polyfill hier .

Bestimmte Leistung
quelle
1
Es ist Stufe 4 und soll in ES2020 landen.
Estus Flask
Auch in Knoten 12 verfügbar :)
Callum M
Selbst wenn die anderen Antworten noch gültig sind, sollte diese mehr positive Stimmen erhalten, da dies die aktuellste Methode zur Lösung dieses Problems ist.
Jacob
9

Ich mag Benjamins Antwort sehr und wie er im Grunde alle Versprechen in immer lösende, aber manchmal mit Fehlern als Ergebnis umwandelt. :)
Hier ist mein Versuch auf Ihre Anfrage, nur für den Fall, dass Sie nach Alternativen suchen. Diese Methode behandelt Fehler einfach als gültige Ergebnisse und ist ähnlich wie Promise.allsonst codiert :

Promise.settle = function(promises) {
  var results = [];
  var done = promises.length;

  return new Promise(function(resolve) {
    function tryResolve(i, v) {
      results[i] = v;
      done = done - 1;
      if (done == 0)
        resolve(results);
    }

    for (var i=0; i<promises.length; i++)
      promises[i].then(tryResolve.bind(null, i), tryResolve.bind(null, i));
    if (done == 0)
      resolve(results);
  });
}
Kuba Wyrostek
quelle
Dies wird normalerweise genannt settle. Wir haben das auch in Bluebird, ich mag es besser zu reflektieren, aber dies ist eine praktikable Lösung, wenn Sie dies für ein Array haben.
Benjamin Gruenbaum
2
OK, Settle wird in der Tat ein besserer Name sein. :)
Kuba Wyrostek
Dies sieht sehr nach dem expliziten Versprechen Bau-Antimuster aus. Es sollte beachtet werden, dass Sie niemals eine solche Funktion selbst schreiben sollten, sondern die von Ihrer Bibliothek bereitgestellte verwenden sollten (OK, natives ES6 ist etwas dürftig).
Bergi
Könnten Sie bitte den PromiseKonstruktor richtig verwenden (und dieses var resolveDing vermeiden )?
Bergi
Bergi, zögern Sie nicht, die Antwort zu ändern, wie Sie es für notwendig halten.
Kuba Wyrostek
5
var err;
Promise.all([
    promiseOne().catch(function(error) { err = error;}),
    promiseTwo().catch(function(error) { err = error;})
]).then(function() {
    if (err) {
        throw err;
    }
});

Das Promise.allwird jedes abgelehnte Versprechen verschlucken und den Fehler in einer Variablen speichern, sodass es zurückkehrt, wenn alle Versprechen gelöst wurden. Dann können Sie den Fehler erneut beseitigen oder was auch immer tun. Auf diese Weise würden Sie wahrscheinlich die letzte Ablehnung anstelle der ersten herausholen.

martin770
quelle
1
Scheint err.push(error)so, als könnte dies Fehler aggregieren, indem es zu einem Array gemacht und verwendet wird , sodass alle Fehler in die Luft gesprudelt werden könnten.
ps2goat
4

Ich hatte das gleiche Problem und habe es folgendermaßen gelöst:

const fetch = (url) => {
  return node-fetch(url)
    .then(result => result.json())
    .catch((e) => {
      return new Promise((resolve) => setTimeout(() => resolve(fetch(url)), timeout));
    });
};

tasks = [fetch(url1), fetch(url2) ....];

Promise.all(tasks).then(......)

In diesem Fall Promise.allwird auf jedes Versprechen gewartet resolvedoder rejectedeingegangen.

Und mit dieser Lösung "stoppen wir die catchAusführung" auf nicht blockierende Weise. Tatsächlich stoppen wir nichts, sondern geben nur das Promisein einem ausstehenden Zustand zurück, das ein anderes zurückgibt, Promisewenn es nach dem Timeout behoben ist.

user1016265
quelle
Aber das ruft alle Versprechen nach Belieben hervor, wenn Sie rennen Promise.all. Ich suche nach einer Möglichkeit, zuzuhören, wenn alle Versprechen aufgerufen wurden, aber nicht selbst. Vielen Dank.
SudoPlz
@SudoPlz die Methode all()macht das, sie wartet auf die Erfüllung aller Versprechen oder die Ablehnung mindestens eines davon.
user1016265
Das stimmt, aber es wartet nicht nur, es ruft tatsächlich den Prozess auf / startet / startet ihn. Wenn Sie die Versprechen an einem anderen Ort auslösen möchten, der nicht möglich wäre, weil .allalles ausgelöst wird.
SudoPlz
@ SudoPlz hoffe, dies wird Ihre Meinung ändern jsfiddle.net/d1z1vey5
user1016265
3
Ich stehe korrigiert. Bis jetzt dachte ich, dass Versprechen nur ausgeführt werden, wenn jemand sie aufruft (auch bekannt als ein thenoder ein .allAnruf), aber sie werden ausgeführt, wenn sie erstellt werden.
SudoPlz
2

Dies sollte mit der Vorgehensweise von Q übereinstimmen :

if(!Promise.allSettled) {
    Promise.allSettled = function (promises) {
        return Promise.all(promises.map(p => Promise.resolve(p).then(v => ({
            state: 'fulfilled',
            value: v,
        }), r => ({
            state: 'rejected',
            reason: r,
        }))));
    };
}
mpen
quelle
2

Die Antwort von Benjamin Gruenbaum ist natürlich großartig. Aber ich kann auch sehen, dass der Standpunkt von Nathan Hagen mit dem Abstraktionsgrad vage erscheint. Kurze Objekteigenschaften wie e & vhelfen auch nicht, aber das könnte natürlich geändert werden.

In Javascript gibt es ein Standardfehlerobjekt namens Error,. Idealerweise werfen Sie immer eine Instanz / einen Nachkommen davon. Der Vorteil ist, dass Sie dies tun instanceof Errorkönnen und wissen, dass etwas ein Fehler ist.

Mit dieser Idee nehme ich das Problem auf.

Fangen Sie den Fehler grundsätzlich ab. Wenn der Fehler nicht vom Typ Fehler ist, schließen Sie den Fehler in ein Fehlerobjekt ein. Das resultierende Array enthält entweder aufgelöste Werte oder Fehlerobjekte, die Sie überprüfen können.

Die Instanz innerhalb des Catch ist für den Fall, dass Sie eine externe Bibliothek verwenden, die dies möglicherweise getan hat reject("error"), anstatt reject(new Error("error")).

Natürlich könnten Sie Versprechen haben, wenn Sie einen Fehler beheben, aber in diesem Fall wäre es höchstwahrscheinlich sinnvoll, ihn trotzdem als Fehler zu behandeln, wie das letzte Beispiel zeigt.

Ein weiterer Vorteil dabei ist, dass die Array-Zerstörung einfach gehalten wird.

const [value1, value2] = PromiseAllCatch(promises);
if (!(value1 instanceof Error)) console.log(value1);

Anstatt

const [{v: value1, e: error1}, {v: value2, e: error2}] = Promise.all(reflect..
if (!error1) { console.log(value1); }

Sie könnten argumentieren, dass die !error1Überprüfung einfacher ist als eine Instanz davon, aber Sie müssen auch beide zerstören v & e.

function PromiseAllCatch(promises) {
  return Promise.all(promises.map(async m => {
    try {
      return await m;
    } catch(e) {
      if (e instanceof Error) return e;
      return new Error(e);
    }
  }));
}


async function test() {
  const ret = await PromiseAllCatch([
    (async () => "this is fine")(),
    (async () => {throw new Error("oops")})(),
    (async () => "this is ok")(),
    (async () => {throw "Still an error";})(),
    (async () => new Error("resolved Error"))(),
  ]);
  console.log(ret);
  console.log(ret.map(r =>
    r instanceof Error ? "error" : "ok"
    ).join(" : ")); 
}

test();

Keith
quelle
2

Anstatt abzulehnen, lösen Sie es mit einem Objekt auf. Sie könnten so etwas tun, wenn Sie Versprechen umsetzen

const promise = arg => {
  return new Promise((resolve, reject) => {
      setTimeout(() => {
        try{
          if(arg != 2)
            return resolve({success: true, data: arg});
          else
            throw new Error(arg)
        }catch(e){
          return resolve({success: false, error: e, data: arg})
        }
      }, 1000);
  })
}

Promise.all([1,2,3,4,5].map(e => promise(e))).then(d => console.log(d))

NuOne
quelle
1
Das sieht gut aus, nicht elegant, wird aber funktionieren
Sunny Tambi
1

Ich denke , die folgenden Angebote ein etwas anderer Ansatz ... Vergleichen fn_fast_fail()mit fn_slow_fail()... obwohl letztere nicht als solche nicht ... Sie , wenn ein oder beide überprüfen aund bist eine Instanz Errorund throwdass , Errorwenn Sie wollen , es zu erreichen der catchBlock (zB if (b instanceof Error) { throw b; }). Siehe die jsfiddle .

var p1 = new Promise((resolve, reject) => { 
    setTimeout(() => resolve('p1_delayed_resolvement'), 2000); 
}); 

var p2 = new Promise((resolve, reject) => {
    reject(new Error('p2_immediate_rejection'));
});

var fn_fast_fail = async function () {
    try {
        var [a, b] = await Promise.all([p1, p2]);
        console.log(a); // "p1_delayed_resolvement"
        console.log(b); // "Error: p2_immediate_rejection"
    } catch (err) {
        console.log('ERROR:', err);
    }
}

var fn_slow_fail = async function () {
    try {
        var [a, b] = await Promise.all([
            p1.catch(error => { return error }),
            p2.catch(error => { return error })
        ]);
        console.log(a); // "p1_delayed_resolvement"
        console.log(b); // "Error: p2_immediate_rejection"
    } catch (err) {
        // we don't reach here unless you throw the error from the `try` block
        console.log('ERROR:', err);
    }
}

fn_fast_fail(); // fails immediately
fn_slow_fail(); // waits for delayed promise to resolve
drmrbrewer
quelle
0

Hier ist mein Brauch settledPromiseAll()

const settledPromiseAll = function(promisesArray) {
  var savedError;

  const saveFirstError = function(error) {
    if (!savedError) savedError = error;
  };
  const handleErrors = function(value) {
    return Promise.resolve(value).catch(saveFirstError);
  };
  const allSettled = Promise.all(promisesArray.map(handleErrors));

  return allSettled.then(function(resolvedPromises) {
    if (savedError) throw savedError;
    return resolvedPromises;
  });
};

Verglichen mit Promise.all

  • Wenn alle Versprechen erfüllt sind, funktioniert es genau wie das Standardversprechen.

  • Wenn eines oder mehrere Versprechen abgelehnt werden, gibt es das erste zurück, das ähnlich wie das Standardversprechen abgelehnt wurde, wartet jedoch im Gegensatz dazu darauf, dass alle Versprechen gelöst / abgelehnt werden.

Für die Mutigen könnten wir uns ändern Promise.all():

(function() {
  var stdAll = Promise.all;

  Promise.all = function(values, wait) {
    if(!wait)
      return stdAll.call(Promise, values);

    return settledPromiseAll(values);
  }
})();

VORSICHT . Im Allgemeinen ändern wir niemals integrierte Funktionen, da dies andere nicht verwandte JS-Bibliotheken beschädigen oder mit zukünftigen Änderungen der JS-Standards in Konflikt geraten kann.

My settledPromiseallist abwärtskompatibel Promise.allund erweitert seine Funktionalität.

Menschen, die Standards entwickeln - warum nicht in einen neuen Promise-Standard aufnehmen?

Edward
quelle
0

Promise.allmit modernen async/awaitAnsatz

const promise1 = //...
const promise2 = //...

const data = await Promise.all([promise1, promise2])

const dataFromPromise1 = data[0]
const dataFromPromise2 = data[1]
Maksim Shamihulau
quelle
-1

Ich würde tun:

var err = [fetch('index.html').then((success) => { return Promise.resolve(success); }).catch((e) => { return Promise.resolve(e); }),
fetch('http://does-not-exist').then((success) => { return Promise.resolve(success); }).catch((e) => { return Promise.resolve(e); })];

Promise.all(err)
.then(function (res) { console.log('success', res) })
.catch(function (err) { console.log('error', err) }) //never executed
FRocha
quelle
-1

Sie können Ihre Logik nacheinander über nsynjs des synchronen Executors ausführen . Es wird bei jedem Versprechen angehalten, auf die Lösung / Ablehnung gewartet und entweder das Ergebnis der Auflösung der dataEigenschaft zugewiesen oder eine Ausnahme ausgelöst (für die Behandlung, für die Sie einen Try / Catch-Block benötigen). Hier ist ein Beispiel:

function synchronousCode() {
    function myFetch(url) {
        try {
            return window.fetch(url).data;
        }
        catch (e) {
            return {status: 'failed:'+e};
        };
    };
    var arr=[
        myFetch("https://ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js"),
        myFetch("https://ajax.googleapis.com/ajax/libs/jquery/2.0.0/NONEXISTANT.js"),
        myFetch("https://ajax.NONEXISTANT123.com/ajax/libs/jquery/2.0.0/NONEXISTANT.js")
    ];
    
    console.log('array is ready:',arr[0].status,arr[1].status,arr[2].status);
};

nsynjs.run(synchronousCode,{},function(){
    console.log('done');
});
<script src="https://rawgit.com/amaksr/nsynjs/master/nsynjs.js"></script>

amaksr
quelle
-1

Ich verwende seit ES5 folgende Codes.

Promise.wait = function(promiseQueue){
    if( !Array.isArray(promiseQueue) ){
        return Promise.reject('Given parameter is not an array!');
    }

    if( promiseQueue.length === 0 ){
        return Promise.resolve([]);
    }

    return new Promise((resolve, reject) =>{
        let _pQueue=[], _rQueue=[], _readyCount=false;
        promiseQueue.forEach((_promise, idx) =>{
            // Create a status info object
            _rQueue.push({rejected:false, seq:idx, result:null});
            _pQueue.push(Promise.resolve(_promise));
        });

        _pQueue.forEach((_promise, idx)=>{
            let item = _rQueue[idx];
            _promise.then(
                (result)=>{
                    item.resolved = true;
                    item.result = result;
                },
                (error)=>{
                    item.resolved = false;
                    item.result = error;
                }
            ).then(()=>{
                _readyCount++;

                if ( _rQueue.length === _readyCount ) {
                    let result = true;
                    _rQueue.forEach((item)=>{result=result&&item.resolved;});
                    (result?resolve:reject)(_rQueue);
                }
            });
        });
    });
};

Die Verwendungssignatur ist genau wie Promise.all. Der Hauptunterschied besteht darin, dass Promise.waitauf alle Versprechen gewartet wird, um ihre Arbeit zu beenden.

user2273990
quelle
-1

Ich weiß, dass diese Frage viele Antworten hat, und ich bin sicher, dass (wenn nicht alle) richtig sein müssen. Es war jedoch sehr schwer für mich, die Logik / den Ablauf dieser Antworten zu verstehen.

Also habe ich mir die ursprüngliche Implementierung angeschaut Promise.all()und versucht, diese Logik nachzuahmen - mit der Ausnahme, dass die Ausführung nicht gestoppt wird, wenn ein Versprechen fehlschlägt.

  public promiseExecuteAll(promisesList: Promise<any>[] = []): Promise<{ data: any, isSuccess: boolean }[]>
  {
    let promise: Promise<{ data: any, isSuccess: boolean }[]>;

    if (promisesList.length)
    {
      const result: { data: any, isSuccess: boolean }[] = [];
      let count: number = 0;

      promise = new Promise<{ data: any, isSuccess: boolean }[]>((resolve, reject) =>
      {
        promisesList.forEach((currentPromise: Promise<any>, index: number) =>
        {
          currentPromise.then(
            (data) => // Success
            {
              result[index] = { data, isSuccess: true };
              if (promisesList.length <= ++count) { resolve(result); }
            },
            (data) => // Error
            {
              result[index] = { data, isSuccess: false };
              if (promisesList.length <= ++count) { resolve(result); }
            });
        });
      });
    }
    else
    {
      promise = Promise.resolve([]);
    }

    return promise;
  }

Erläuterung:
- Durchlaufen Sie die Eingabe promisesListund führen Sie jedes Versprechen aus.
- Egal, ob das Versprechen aufgelöst oder abgelehnt wurde: Speichern Sie das Ergebnis des Versprechens in einem resultArray gemäß dem index. Speichern Sie auch den Auflösungs- / Ablehnungsstatus ( isSuccess).
- Wenn alle Versprechen abgeschlossen sind, geben Sie ein Versprechen mit dem Ergebnis aller anderen zurück.

Anwendungsbeispiel:

const p1 = Promise.resolve("OK");
const p2 = Promise.reject(new Error(":-("));
const p3 = Promise.resolve(1000);

promiseExecuteAll([p1, p2, p3]).then((data) => {
  data.forEach(value => console.log(`${ value.isSuccess ? 'Resolve' : 'Reject' } >> ${ value.data }`));
});

/* Output: 
Resolve >> OK
Reject >> :-(
Resolve >> 1000
*/
Gil Epshtain
quelle
2
Versuchen Sie nicht, sich erneut zu implementieren Promise.all, da zu viele Dinge schief gehen. Ihre Version verarbeitet beispielsweise keine leeren Eingaben.
Bergi
-4

Ich weiß nicht, welche Versprechungsbibliothek Sie verwenden, aber die meisten haben so etwas wie allSettled .

Bearbeiten: Ok, da Sie einfaches ES6 ohne externe Bibliotheken verwenden möchten, gibt es keine solche Methode.

Mit anderen Worten: Sie müssen Ihre Versprechen manuell durchlaufen und ein neues kombiniertes Versprechen auflösen, sobald alle Versprechen erfüllt sind.

Sebastian S.
quelle
Ich habe meine Frage bearbeitet, um zu verdeutlichen: Da ES6 mit Versprechungen geliefert wird, möchte ich vermeiden, eine andere Bibliothek für die meiner Meinung nach grundlegende Funktionalität zu verwenden. Ich denke, ein guter Ort, um die Antwort zu erhalten, wäre, die Quelle aus einer der Versprechensbibliotheken zu kopieren.
Nathan Hagen