Attendez que toutes les promesses soient terminées même si certaines ont été rejetées

405

Disons que j'ai un ensemble de Promises qui font des requêtes réseau, dont l'un échouera:

// 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   

Disons que je veux attendre jusqu'à ce que tout cela soit terminé, peu importe si l'un a échoué. Il peut y avoir une erreur de réseau pour une ressource dont je peux vivre sans, mais que si je peux obtenir, je veux avant de continuer. Je veux gérer les pannes de réseau avec élégance.

Comme Promises.allcela ne laisse aucune place à cela, quel est le modèle recommandé pour gérer cela, sans utiliser une bibliothèque de promesses?

Nathan Hagen
la source
Que faut-il retourner dans le tableau résultant pour les promesses rejetées?
Kuba Wyrostek
9
ES6 promet de ne pas prendre en charge une telle méthode (et est actuellement apparemment plus lent que Bluebird ). De plus, tous les navigateurs ou moteurs ne les prennent pas encore en charge. Je recommanderais fortement d' utiliser Bluebird, qui est livré avec allSettledqui répond à vos besoins sans que vous ayez à rouler le vôtre.
Dan Pantry
@KubaWyrostek Je pense que vous évoquez la raison pour laquelle Promise.all n'a pas ce comportement, ce qui, je pense, est logique. Ce n'est pas ainsi que cela fonctionne, mais une autre vue serait de dire que Promise.all devrait renvoyer une promesse spéciale qui n'échoue jamais - et vous obtiendriez l'erreur qui a été lancée comme argument représentant la promesse échouée.
Nathan Hagen
Pour ajouter à ce que Dan a partagé, la fonctionnalité allSettled / settAll like de Bluebird peut être utilisée via la fonction "Reflect".
user3344977
2
@Coli: Hmm, je ne pense pas. Promise.allrejettera dès que toute promesse sera rejetée, donc votre idiome proposé ne garantit pas que toutes les promesses sont réglées.
Jörg W Mittag

Réponses:

310

Mise à jour, vous souhaitez probablement utiliser le natif intégré Promise.allSettled:

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

Comme un fait amusant, cette réponse ci-dessous était l'art antérieur en ajoutant cette méthode à la langue:]


Bien sûr, vous avez juste besoin d'un reflect:

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

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

Ou avec 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);
});

Ou dans votre exemple:

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
la source
3
Je pense que c'est une excellente solution. Pouvez-vous le modifier pour inclure une syntaxe plus simple? Le nœud du problème est que si vous souhaitez gérer les erreurs dans les sous-promesses, vous devez les attraper et renvoyer l'erreur. Ainsi, par exemple: gist.github.com/nhagen/a1d36b39977822c224b8
Nathan Hagen
3
@NathanHagen il vous permet de comprendre ce qui a été rejeté et ce qui a été accompli et extrait le problème à un opérateur réutilisable.
Benjamin Gruenbaum
4
En réponse à mon propre problème, j'ai créé le package npm suivant: github.com/Bucabug/promise-reflect npmjs.com/package/promise-reflect
SamF
2
J'ai rencontré ce problème il y a quelque temps et j'ai créé ce package npm pour cela: npmjs.com/package/promise-all-soft-fail
velocity_distance
5
Le mot est-il reflectun mot courant en informatique? Pouvez-vous s'il vous plaît lien vers où cela est expliqué comme sur wikipedia ou quelque chose. Je cherchais dur Promise.all not even first rejectmais je ne savais pas comment chercher "Reflect". ES6 devrait-il avoir un Promise.reflectqui est comme "Promise.all mais vraiment tout"?
Noitidart
253

Réponse similaire, mais plus idiomatique pour ES6 peut-être:

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>

Selon le (s) type (s) de valeurs renvoyées, les erreurs peuvent souvent être distinguées assez facilement (par exemple, utiliser undefinedpour "indifférent", typeofpour des valeurs non-objet simples result.message, result.toString().startsWith("Error:")etc.)

foc
la source
1
@KarlBateman Je pense que vous êtes confus. Les fonctions d'ordre .map(p => p.catch(e => e))résolues ou rejetées n'ont pas d'importance ici car la pièce transforme tous les rejets en valeurs résolues, donc Promise.allattend toujours que tout se termine, que les fonctions individuelles soient résolues ou rejetées, quel que soit le temps qu'elles prennent. Essayez-le.
foc
39
.catch(e => console.log(e));n'est jamais appelé parce que cela n'échoue jamais
fregante
4
@ bfred.it C'est vrai. Bien que mettre fin aux chaînes de promesses avec catchest généralement une bonne pratique à mon humble avis .
foc
2
@SuhailGupta Il intercepte l'erreur eet la renvoie en tant que valeur régulière (succès). Identique à p.catch(function(e) { return e; })seulement plus court. returnest implicite.
foc
1
@JustinReusnow déjà couvert dans les commentaires. Toujours une bonne pratique pour terminer les chaînes au cas où vous ajouteriez du code plus tard.
foc
71

La réponse de Benjamin offre une excellente abstraction pour résoudre ce problème, mais j'espérais une solution moins abstraite. La manière explicite de résoudre ce problème consiste simplement à faire appel .catchaux promesses internes et à renvoyer l'erreur à partir de leur rappel.

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));

En allant plus loin, vous pouvez écrire un gestionnaire de capture générique qui ressemble à ceci:

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

alors tu peux faire

> 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 } ]

Le problème avec cela est que les valeurs capturées auront une interface différente de celle des valeurs non capturées, donc pour nettoyer cela, vous pourriez faire quelque chose comme:

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

Alors maintenant, vous pouvez le faire:

> 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!' ]

Ensuite, pour le garder au sec, vous obtenez la réponse de Benjamin:

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

où il ressemble maintenant

> 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!' ]

Les avantages de la deuxième solution sont qu'elle est abstraite et sèche. L'inconvénient est que vous avez plus de code, et vous devez vous rappeler de refléter toutes vos promesses pour rendre les choses cohérentes.

Je caractériserais ma solution comme explicite et KISS, mais en effet moins robuste. L'interface ne garantit pas que vous savez exactement si la promesse a réussi ou échoué.

Par exemple, vous pourriez avoir ceci:

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

Cela ne sera pas rattrapé a.catch, alors

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

Il n'y a aucun moyen de dire lequel a été fatal et lequel ne l'a pas été. Si c'est important, vous allez vouloir appliquer et une interface qui suit si elle a réussi ou non (ce qui est le reflectcas).

Si vous souhaitez simplement gérer les erreurs avec élégance, vous pouvez simplement traiter les erreurs comme des valeurs non définies:

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

Dans mon cas, je n'ai pas besoin de connaître l'erreur ou comment elle a échoué - je me soucie simplement si j'ai la valeur ou non. Je laisse la fonction qui génère la promesse s'inquiéter de la journalisation de l'erreur spécifique.

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

De cette façon, le reste de l'application peut ignorer son erreur s'il le souhaite et la traiter comme une valeur indéfinie s'il le souhaite.

Je veux que mes fonctions de haut niveau échouent en toute sécurité et ne se soucient pas des détails sur les raisons de l'échec de ses dépendances, et je préfère également KISS à DRY lorsque je dois faire ce compromis - c'est pourquoi j'ai finalement choisi de ne pas utiliser reflect.

Nathan Hagen
la source
1
@Benjamin Je pense que la solution de @ Nathan est très simple et idiomatique pour Promises. Tandis que votre reflectaméliore la réutilisation du code, il établit également un autre niveau d'abstraction. Étant donné que la réponse de Nathan n'a jusqu'à présent reçu qu'une fraction des votes positifs par rapport à la vôtre, je me demande si cela indique un problème avec sa solution, que je n'ai pas encore reconnu.
2
@ LUH3417 cette solution est conceptuellement moins solide car elle traite les erreurs comme des valeurs et ne sépare pas les erreurs des non-erreurs. Par exemple, si l'une des promesses se résout légitimement à une valeur qui peut être lancée (ce qui est tout à fait possible), cela casse assez mal.
Benjamin Gruenbaum
2
@BenjaminGruenbaum Ainsi, par exemple, new Promise((res, rej) => res(new Error('Legitimate error'))ne se distinguerait-il pas new Promise(((res, rej) => rej(new Error('Illegitimate error'))? Ou plus loin, vous ne pourriez pas filtrer x.status? J'ajouterai ce point à ma réponse pour que la différence soit plus claire
Nathan Hagen
3
La raison pour laquelle c'est une mauvaise idée est parce qu'elle lie l'implémentation de Promise à un cas d'utilisation spécifique de n'être utilisé que dans une Promise.all()variante spécifique , il incombe également au consommateur de Promise de savoir qu'une promesse spécifique ne rejettera pas mais sera avaler ce sont des erreurs. En fait, la reflect()méthode pourrait être rendue moins «abstraite» et plus explicite en l'appelant. PromiseEvery(promises).then(...)La complexité de la réponse ci-dessus par rapport à celle de Benjamin devrait en dire long sur cette solution.
Neil
33

Il y a une proposition finalisée pour une fonction qui peut accomplir cela nativement, en vanilla Javascript Promise.allSettled:, qui a atteint l'étape 4, est officialisé dans ES2020, et est implémenté dans tous les environnements modernes . Elle est très similaire à la reflectfonction de cette autre réponse . Voici un exemple, de la page de proposition. Avant, vous auriez dû faire:

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');

En utilisant à la Promise.allSettledplace, ce qui précède sera équivalent à:

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

Ceux qui utilisent des environnements modernes pourront utiliser cette méthode sans aucune bibliothèque . Dans ces cas, l'extrait de code suivant devrait s'exécuter sans problème:

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

Production:

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

Pour les navigateurs plus anciens, il y a un polyfill conforme spécifications ici .

CertainPerformance
la source
1
C'est l'étape 4 et devrait atterrir dans ES2020.
Estus Flask
Également disponible dans le noeud 12 :)
Callum M
Même si les autres réponses sont toujours valables, celle-ci devrait obtenir plus de votes positifs car c'est la façon la plus courante de résoudre ce problème.
Jacob
9

J'aime vraiment la réponse de Benjamin, et comment il transforme fondamentalement toutes les promesses en engagements toujours résolus mais parfois avec erreur. :)
Voici ma tentative à votre demande au cas où vous cherchiez des alternatives. Cette méthode traite simplement les erreurs comme des résultats valides et est codée de la même Promise.allmanière:

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
la source
Ceci est généralement appelé settle. Nous avons cela aussi dans bluebird, j'aime mieux réfléchir mais c'est une solution viable pour quand vous avez ceci pour un tableau.
Benjamin Gruenbaum
2
OK, régler sera un meilleur nom en effet. :)
Kuba Wyrostek
Cela ressemble beaucoup à l'anti-modèle de construction de promesse explicite. Il convient de noter que vous ne devriez jamais écrire une telle fonction vous-même, mais utilisez celle fournie par votre bibliothèque (OK, ES6 natif est un peu maigre).
Bergi
Pourriez-vous s'il vous plaît utiliser le Promiseconstructeur correctement (et éviter ce var resolvetruc)?
Bergi
Bergi, n'hésitez pas à modifier la réponse comme vous le jugez nécessaire.
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;
    }
});

Le Promise.allva avaler toute promesse rejetée et stocker l'erreur dans une variable, il reviendra donc lorsque toutes les promesses auront été résolues. Ensuite, vous pouvez renvoyer l'erreur ou faire n'importe quoi. De cette façon, je suppose que vous obtiendrez le dernier rejet au lieu du premier.

martin770
la source
1
Il semble que cela puisse agréger err.push(error)les erreurs en en faisant un tableau et en l'utilisant , de sorte que toutes les erreurs puissent être propagées.
ps2goat
4

J'ai eu le même problème et je l'ai résolu de la manière suivante:

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(......)

Dans ce cas , Promise.allvous attendra chaque promesse entrera en resolvedou rejectedétat.

Et avec cette solution, nous «arrêtons l' catchexécution» de manière non bloquante. En fait, nous n'arrêtons rien, nous retournons simplement le Promisedans un état en attente qui en retourne un autre Promiselorsqu'il est résolu après le délai d'expiration.

user1016265
la source
Mais cela invoque toutes les promesses à volonté lorsque vous courez Promise.all. Je cherche un moyen d'écouter quand toutes les promesses ont été invoquées, mais pas de les invoquer moi-même. Merci.
SudoPlz
@SudoPlz la méthode all()fait cela, elle attend la réalisation de toutes les promesses ou le rejet d'au moins une d'entre elles.
user1016265
c'est vrai, mais cela n'attend pas seulement, il invoque / démarre / déclenche le processus. Si vous souhaitiez faire des promesses ailleurs qui ne seraient pas possibles, parce que .alltout se déclenche.
SudoPlz
@SudoPlz espère que cela changera votre opinion jsfiddle.net/d1z1vey5
user1016265
3
Je me suis trompé. Jusqu'à présent, je pensais que les promesses ne s'exécutaient que lorsque quelqu'un les invoquait (alias un thenou un .allappel) mais qu'elles s'exécutaient lorsqu'elles étaient créées.
SudoPlz
2

Cela devrait être cohérent avec la façon dont Q le fait :

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
la source
2

La réponse de Benjamin Gruenbaum est bien sûr excellente. Mais je peux aussi voir si le point de vue de Nathan Hagen avec le niveau d'abstraction semble vague. Avoir des propriétés d'objet courtes comme e & vn'aide pas non plus, mais bien sûr, cela pourrait être changé.

En Javascript, il existe un objet Error standard, appelé Error,. Idéalement, vous jetez toujours une instance / un descendant de cela. L'avantage est que vous pouvez le faire instanceof Error, et vous savez que quelque chose est une erreur.

Donc, en utilisant cette idée, voici mon point de vue sur le problème.

En gros, intercepter l'erreur, si l'erreur n'est pas de type Erreur, encapsuler l'erreur dans un objet Error. Le tableau résultant aura soit des valeurs résolues, soit des objets d'erreur que vous pourrez vérifier.

L'instance de l'intérieur de la capture, est au cas où vous utilisez une bibliothèque externe qui pourrait le faire reject("error"), au lieu de reject(new Error("error")).

Bien sûr, vous pourriez avoir des promesses si vous résolviez une erreur, mais dans ce cas, il serait très logique de traiter de toute façon une erreur, comme le montre le dernier exemple.

Un autre avantage de le faire, la destruction de tableaux reste simple.

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

Au lieu de

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

Vous pourriez faire valoir que la !error1vérification est plus simple qu'une instance de, mais vous devez également détruire les deux 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
la source
2

Au lieu de rejeter, résolvez-le avec un objet. Vous pouvez faire quelque chose comme ça lorsque vous mettez en œuvre la promesse

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
la source
1
Cela semble être un bon travail, pas élégant mais fonctionnera
Sunny Tambi
1

Je pense que ce qui suit offre une approche légèrement différente ... comparer fn_fast_fail()avec fn_slow_fail()... bien que ce dernier n'échoue pas en tant que tel ... vous pouvez vérifier si l'un ou les deux aet best une instance de Erroret throwque Errorsi vous voulez qu'il atteigne le catchbloc (par exemple if (b instanceof Error) { throw b; }). Voir le 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
la source
0

Voici ma coutume 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;
  });
};

Par rapport à Promise.all

  • Si toutes les promesses sont résolues, il fonctionne exactement comme celui standard.

  • Si une ou plusieurs promesses sont rejetées, elle renvoie la première rejetée de la même manière que la norme, mais contrairement à elle, elle attend que toutes les promesses soient résolues / rejetées.

Pour les courageux, nous pourrions changer Promise.all():

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

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

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

ATTENTION . En général, nous ne modifions jamais les fonctionnalités intégrées, car cela pourrait casser d'autres bibliothèques JS non liées ou se heurter à de futures modifications des normes JS.

My settledPromiseallest rétrocompatible avec Promise.allet étend ses fonctionnalités.

Les personnes qui développent des normes - pourquoi ne pas les inclure dans une nouvelle norme Promise?

Edward
la source
0

Promise.allavec une async/awaitapproche moderne

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

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

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

Je ferais:

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
la source
-1

Vous pouvez exécuter votre logique séquentiellement via l'exécuteur synchrone nsynjs . Il s'arrêtera sur chaque promesse, attendra la résolution / rejet et affectera le résultat de la résolution à la datapropriété ou lèvera une exception (pour la gestion dont vous aurez besoin du bloc try / catch). Voici un exemple:

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
la source
-1

J'utilise les codes suivants depuis ES5.

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);
                }
            });
        });
    });
};

La signature d'utilisation est exactement comme Promise.all. La principale différence est qu'il Promise.waitattendra toutes les promesses pour terminer son travail.

user2273990
la source
-1

Je sais que cette question a beaucoup de réponses, et je suis sûr qu'elle doit (sinon toutes) être correcte. Cependant, il m'a été très difficile de comprendre la logique / le flux de ces réponses.

J'ai donc regardé la mise en œuvre d'origine Promise.all()et j'ai essayé d'imiter cette logique - à l'exception de ne pas arrêter l'exécution si une promesse échouait.

  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;
  }

Explication:
- Faites une boucle sur l'entrée promisesListet exécutez chaque promesse.
- Peu importe si la promesse a été résolue ou rejetée: enregistrez le résultat de la promesse dans un resulttableau selon le index. Enregistrez également le statut de résolution / rejet ( isSuccess).
- Une fois toutes les promesses terminées, renvoyez une promesse avec le résultat de toutes les autres.

Exemple d'utilisation:

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
la source
2
N'essayez pas de Promise.allvous réimplémenter, il y a trop de choses qui vont mal se passer. Votre version ne gère pas les entrées vides par exemple.
Bergi
-4

Je ne sais pas quelle bibliothèque de promesses vous utilisez, mais la plupart ont quelque chose comme allSettled .

Edit: Ok puisque vous voulez utiliser ES6 ordinaire sans bibliothèques externes, il n'y a pas une telle méthode.

En d'autres termes: vous devez parcourir vos promesses manuellement et résoudre une nouvelle promesse combinée dès que toutes les promesses sont réglées.

Sebastian S
la source
J'ai édité ma question pour clarifier - Puisque ES6 est livré avec des promesses, j'aimerais éviter d'utiliser une autre bibliothèque pour ce que je pense être une fonctionnalité de base. Je suppose qu'un bon endroit pour obtenir la réponse serait de copier la source de l'une des bibliothèques de promesses.
Nathan Hagen