Mit mehreren Promises gleichzeitig arbeiten

Callbacks waren in JavaScript lange ein notwendiges Übel, um mit der asynchronen Natur der Sprache umzugehen. Promises machen in ECMAScript 6 endlich Schluss damit. Statt einer verschachtelten Callback-Struktur, kann man nun Operationen sauber miteinander verketten. Doch richtig spannend wird es erst, wenn man mehrere asynchrone Operationen auf einmal ausführen möchte. Ich gehe davon aus, dass Ihr Euch schon mit Promises beschäftigt habt. Für alle anderen gibt es zu Beginn eine kleine Zusammenfassung.

Was ist eine Promise?

Ein Promise stellt die eventuelle Auflösung einer Operation dar, unabhängig davon von, ob die Operation nun erfolgreich ist oder fehl schlägt. Klingt ziemlich technisch und kompliziert. Ist es aber eigentlich gar nicht.

Stellt Euch einfach vor, dass Ihr einen Ajax-Request durchführt. Dieser dauert eine gewisse Zeit, wie lang genau wisst Ihr leider nicht. Früher hätte Ihr hier also mit einem Callback gearbeitet. Das Request kann erfolgreich sein, aber es kann auch fehl schlagen. Zum Beispiel weil der Server nicht erreichbar ist. Das kann dann in Form einer Promise ungefähr so aussehen:

var myPromise = new Promise(function(resolve, reject) {
  // Irgendein asynchroner Code, beispielsweise ein Ajax-Request.
  // ...

  if (isSuccessful) {
    resolve('Hat geklappt!');
  }
  else {
    reject('Irgendwas ist schief gelaufen.');
  }
});

myPromise.then(function (result) {
  // Wird getriggert, wenn resolve() aufgerufen wird.
  // result: 'Hat geklappt!'
});

myPromise.catch(function (reason) {
  // Wird getriggert, wenn reject() aufgerufen wird.
  // reason: 'Irgendwas ist schief gelaufen.'
});

Wie kann man mehrere Promises gleichzeitig verarbeiten?

Wir können aber nicht nur einzelne Promises verwenden, sondern mehrere Operationen gleichzeitig laufen lassen. Also beispielsweise mehrere Ajax-Requests. Dafür gibt es zum einen Promise.all() und zum anderen Promise.race(). Promise.all() wird nur dann erfolgreich aufgelöst (resolved), wenn alle Operationen erfolgreich waren. Promise.race() wird aufgelöst, sobald die erste Operation entweder erfolgreich ist oder fehl schlägt. Bei den Dev Docs von Mozilla gibt es hier zwei schöne, interaktive Beispiele.

Promise.all(): Nur gemeinsam sind wir aufgelöst.

var promise1 = Promise.resolve(3);
var promise2 = 42;
var promise3 = new Promise(function(resolve, reject) {
  setTimeout(resolve, 100, 'foo');
});

Promise.all([promise1, promise2, promise3]).then(function(values) {
  console.log(values);
});
// expected output: Array [3, 42, "foo"]

Hier geht's zur Quelle.

Promise.race(): Wer zuerst kommt, löst zuerst auf.

var promise1 = new Promise(function(resolve, reject) {
    setTimeout(resolve, 500, 'one');
});

var promise2 = new Promise(function(resolve, reject) {
    setTimeout(resolve, 100, 'two');
});

Promise.race([promise1, promise2]).then(function(value) {
  console.log(value);
  // Both resolve, but promise2 is faster
});
// expected output: "two"

Hier geht's zur Quelle.

Promise.some(): Der Sonderfall

Die beiden obigen Optionen decken schon eine Menge Fälle ab. Aber manchmal ist es nicht so wichtig, dass alle Requests erfolgreich sind. Beispielsweise wenn man Daten aus verschiedenen Quellen zusammen führen möchte. Eventuell spielt es dann keine Rolle, wenn ein oder zwei Quellen nicht erreichbar sind. Darum habe ich das Promise-Objekt noch ein bisschen erweitert und Promise.some() eingeführt.

/**
 * Returns a promise that resolves when all given promises are resolved or rejected.
 *
 * @param {Promise[]} promises
 *   Array of promises.
 *
 * @return {Promise<any[]>}
 *   Promises which resolves in array of results.
 *
 * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all
 */
Promise.some = function (promises) {
  return new Promise(resolve => {
    let results = [];
    let resultsNum = 0;
    let promisesNum = promises.length;
    for (let i = 0; i < promisesNum; i++) {
      promises[i].then(result => {
        results[i] = result;
        resultsNum++;

        if (resultsNum === promisesNum) {
          resolve(results);
        }
      }).catch(() => {
        results[i] = null;
        resultsNum++;

        if (resultsNum === promisesNum) {
          resolve(results);
        }
      });
    }
  });
};

Promise.some() wird immer erfolgreich aufgelöst, sobald alle Request abgeschlossen sind. Wem das zu unsauber ist, der kann die Funktion dahin gehend erweitern, dass zumindest dann "rejected" wird, wenn alle Promises fehl schlagen. Lasst mich wissen, was Ihr von der Idee haltet.


Kommentare


Wichtig: Durch den Click auf die obige Checkbox stimmst Du ausdrücklich der Übertragung von Daten an Facebook zu. Die Zustimmung erfolgt einmalig für die Kommentare dieses Artikels und wird nicht gespeichert. Weitere Details kannst Du dem Punkt Social Plugins der Datenschutzerklärung entnehmen.