Attendi il completamento di tutte le promesse anche se alcune sono state respinte


405

Diciamo che ho una serie di messaggi Promiseche stanno facendo richieste di rete, di cui uno fallirà:

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

Diciamo che voglio aspettare fino a quando tutti questi non avranno terminato, indipendentemente dal fatto che uno abbia fallito. Potrebbe esserci un errore di rete per una risorsa senza la quale posso vivere, ma che se posso ottenere, voglio prima di procedere. Voglio gestire gli errori di rete con garbo.

Dal momento Promises.allche non lascia spazio a ciò, qual è lo schema raccomandato per gestirlo, senza usare una libreria di promesse?


Cosa dovrebbe essere restituito nella matrice risultante per le promesse respinte?
Kuba Wyrostek,

9
Le promesse ES6 non supportano tale metodo (e attualmente sono apparentemente più lente di Bluebird ). Inoltre, non tutti i browser o motori li supportano ancora. Consiglio vivamente di utilizzare Bluebird, che viene fornito con il allSettledquale soddisfa le tue esigenze senza che tu debba farlo da solo.
Dan Pantry,

@KubaWyrostek Penso che tu abbia sollevato il motivo per cui Promise.all non ha questo comportamento, che penso abbia senso. Non è così che funziona, ma una visione alternativa sarebbe dire Promessa. Tutti dovrebbero restituire una promessa speciale che non fallisce mai - e otterresti l'errore che è stato lanciato come argomento che rappresenta la promessa fallita.
Nathan Hagen,

Per aggiungere ciò che Dan ha condiviso, la funzionalità allSettled / settleAll like di bluebird può essere utilizzata tramite la funzione "riflett".
user3344977,

2
@Coli: Hmm, non credo. Promise.allrifiuterà non appena una delle promesse verrà respinta , quindi il tuo linguaggio proposto non garantisce che tutte le promesse siano state rispettate.
Jörg W Mittag,

Risposte:


310

Aggiornamento, probabilmente si desidera utilizzare il nativo incorporato Promise.allSettled:

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

È un fatto divertente, questa risposta era la tecnica precedente per aggiungere quel metodo alla lingua:]


Certo, hai solo bisogno di un reflect:

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

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

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

O nel tuo esempio:

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

3
Penso che questa sia un'ottima soluzione. Puoi modificarlo per includere una sintassi più semplice? Il punto cruciale del problema è che se si desidera gestire gli errori nelle promesse secondarie, è necessario rilevarli e restituire l'errore. Quindi ad esempio: gist.github.com/nhagen/a1d36b39977822c224b8
Nathan Hagen

3
@NathanHagen ti permette di capire cosa è stato rifiutato e cosa è stato soddisfatto ed estrae il problema da un operatore riutilizzabile.
Benjamin Gruenbaum,

4
In risposta al mio problema ho creato il seguente pacchetto npm: github.com/Bucabug/promise-reflect npmjs.com/package/promise-reflect
SamF

2
Ho riscontrato questo problema qualche tempo fa e ho creato questo pacchetto npm per questo: npmjs.com/package/promise-all-soft-fail
velocity_distance

5
La parola è reflectuna parola comune nell'informatica? Potete per favore collegarvi a dove è spiegato come su Wikipedia o qualcosa del genere. Stavo cercando molto Promise.all not even first rejectma non sapevo cercare "Rifletti". ES6 dovrebbe avere un Promise.reflecttipo "Promise.all ma davvero tutto"?
Noitidart,

253

Risposta simile, ma forse più idiomatica per 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>

A seconda del tipo o dei tipi di valori restituiti, gli errori possono spesso essere distinti abbastanza facilmente (ad es. Usare undefinedper "non importa", typeofper semplici valori non oggetto result.message, result.toString().startsWith("Error:")ecc.)


1
@KarlBateman Penso che tu sia confuso. Le funzioni di ordine in cui si risolvono o rifiutano non contano qui poiché la .map(p => p.catch(e => e))parte trasforma tutti i rifiuti in valori risolti, quindi Promise.allattende ancora che tutto finisca se le singole funzioni si risolvono o rifiutano, indipendentemente da quanto tempo impiegano. Provalo.
fiocco

39
.catch(e => console.log(e));non viene mai chiamato perché non fallisce mai
fregante

4
@ bfred.it È corretto. Sebbene terminare le catene di promesse con IMHOcatch è generalmente una buona pratica .
braccio

2
@SuhailGupta Rileva l'errore ee lo restituisce come valore normale (successo). Come p.catch(function(e) { return e; })solo più corto. returnè implicito.
fiocco

1
@JustinReusnow già trattato nei commenti. Sempre buona pratica per terminare le catene nel caso in cui si aggiunga codice in un secondo momento.
fiocco

71

La risposta di Benjamin offre una grande astrazione per risolvere questo problema, ma speravo in una soluzione meno astratta. Il modo esplicito di risolvere questo problema è semplicemente chiamare .catchle promesse interne e restituire l'errore dal loro callback.

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

Facendo un ulteriore passo avanti, potresti scrivere un gestore di cattura generico che assomigli a questo:

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

allora puoi farlo

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

Il problema con questo è che i valori catturati avranno un'interfaccia diversa rispetto ai valori non catturati, quindi per ripulire questo potresti fare qualcosa del tipo:

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

Quindi ora puoi farlo:

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

Quindi per mantenerlo SECCO, si arriva alla risposta di Benjamin:

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

dove ora sembra

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

I vantaggi della seconda soluzione sono che è astratto e SECCO. L'aspetto negativo è che hai più codice e devi ricordare di riflettere tutte le tue promesse per rendere le cose coerenti.

Definirei la mia soluzione esplicita e KISS, ma in effetti meno robusta. L'interfaccia non garantisce che tu sappia esattamente se la promessa ha avuto successo o meno.

Ad esempio potresti avere questo:

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

Questo non verrà catturato da a.catch, quindi

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

Non c'è modo di dire quale fosse fatale e quale no. Se questo è importante, ti consigliamo di imporre e interfacciare che traccia se ha avuto successo o meno (il che lo reflectfa).

Se vuoi solo gestire gli errori con garbo, puoi semplicemente considerare gli errori come valori non definiti:

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

Nel mio caso, non ho bisogno di sapere l'errore o come è fallito - mi importa solo se ho il valore o no. Lascerò che la funzione che genera la promessa si preoccupi della registrazione dell'errore specifico.

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

In questo modo, il resto dell'applicazione può ignorare il suo errore se lo desidera e trattarlo come un valore indefinito se lo desidera.

Voglio che le mie funzioni di alto livello falliscano in modo sicuro e non si preoccupino dei dettagli sul perché le sue dipendenze hanno fallito, e preferisco anche KISS a ASCIUGARE quando devo fare quel compromesso - che alla fine ho deciso di non usare reflect.


1
@Benjamin Penso che la soluzione di @ Nathan sia molto semplice e idiomatica per Promises. Mentre reflectmigliora il riutilizzo del codice, stabilisce anche un altro livello di astrazione. Dato che finora la risposta di Nathan ha ricevuto solo una piccola parte dei voti rispetto alla tua, mi chiedo se ciò sia indicativo di un problema con la sua soluzione, che non ho ancora riconosciuto.

2
@ LUH3417 questa soluzione è concettualmente meno valida poiché tratta gli errori come valori e non separa gli errori dai non errori. Ad esempio, se una delle promesse si risolve legittimamente in un valore che può essere lanciato (il che è del tutto possibile), questo si rompe abbastanza male.
Benjamin Gruenbaum,

2
@BenjaminGruenbaum Quindi, per esempio, new Promise((res, rej) => res(new Error('Legitimate error'))non sarebbe distinguibile da new Promise(((res, rej) => rej(new Error('Illegitimate error'))? O ancora, non saresti in grado di filtrare per x.status? Aggiungerò questo punto alla mia risposta in modo che la differenza sia più chiara
Nathan Hagen il

3
Il motivo per cui questa è una cattiva idea è perché lega l'implementazione Promise a un caso d'uso specifico di essere mai usato solo in uno specifico Promise.all() variante, quindi diventa anche il consumatore Promessa a sapere che una promessa specifica non rifiuta ma lo farà ingoiare sono errori. In effetti il reflect()metodo potrebbe essere reso meno "astratto" e più esplicito chiamandolo PromiseEvery(promises).then(...). La complessità della risposta sopra rispetto a quella di Benjamin dovrebbe dire molto su questa soluzione.
Neil,

33

C'è una proposta finita per una funzione che può compiere questo in modo nativo, in Javascript vaniglia: Promise.allSettledche è arrivato allo stadio 4, è ufficializzato in ES2020 ed è implementato in tutti gli ambienti moderni . È molto simile alla reflectfunzione in questa altra risposta . Ecco un esempio, dalla pagina della proposta. Prima avresti dovuto fare:

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

Utilizzando Promise.allSettledinvece, quanto sopra sarà equivalente a:

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

Chi utilizza ambienti moderni sarà in grado di utilizzare questo metodo senza librerie . In questi, il seguente frammento dovrebbe essere eseguito senza problemi:

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

Produzione:

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

Per i browser meno recenti, esiste un polyfill conforme alle specifiche qui .


1
È la fase 4 e dovrebbe atterrare in ES2020.
Estus Flask,

Disponibile anche nel nodo 12 :)
Callum M

Anche se le altre risposte sono ancora valide, questa dovrebbe ottenere più voti in quanto è il modo più attuale di risolvere questo problema.
Jacob

9

Mi piace molto la risposta di Benjamin e come fondamentalmente trasforma tutte le promesse in sempre risolventi, ma a volte con errori come risultato. :)
Ecco il mio tentativo su tua richiesta nel caso in cui stavi cercando alternative. Questo metodo tratta semplicemente gli errori come risultati validi ed è codificato in modo simile al Promise.allcontrario:

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

Questo è in genere chiamato settle. Lo abbiamo anche in bluebird, mi piace riflettere meglio, ma questa è una soluzione praticabile per quando hai questo per un array.
Benjamin Gruenbaum,

2
OK, accontentarsi sarà davvero un nome migliore. :)
Kuba Wyrostek,

Questo assomiglia molto al modello di costruzione esplicita della promessa. Va notato che non dovresti mai scrivere una simile funzione da solo, ma usa quella fornita dalla tua libreria (OK, ES6 nativo è un po 'scarso).
Bergi,

Potresti per favore usare Promisecorrettamente il costruttore (ed evitare quella var resolvecosa)?
Bergi,

Bergi, sentiti libero di modificare la risposta come ritieni necessario.
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;
    }
});

Il Promise.allingoiare qualsiasi promessa respinto e conservare l'errore in una variabile, in modo che tornerà quando tutte le promesse si sono risolti. Quindi puoi respingere l'errore o fare qualunque cosa. In questo modo, immagino che usciresti dall'ultimo rifiuto invece che dal primo.


1
Sembra che questo potrebbe aggregare gli err.push(error)errori rendendolo un array e usando , quindi tutti gli errori potrebbero essere confusi.
ps2goat,

4

Ho avuto lo stesso problema e l'ho risolto nel modo seguente:

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 tal caso Promise.all, aspetterà che ogni Promessa entrerà resolvedo rejecteddichiarerà.

E avendo questa soluzione "interrompiamo l' catchesecuzione" in modo non bloccante. In effetti, non stiamo arrestando nulla, stiamo solo tornando indietro Promisein uno stato in sospeso che ne restituisce un altro Promisequando viene risolto dopo il timeout.


Ma ciò invoca tutte le promesse a volontà quando corri Promise.all. Sto cercando un modo per ascoltare quando tutte le promesse sono state invocate, ma non invocarle da solo. Grazie.
SudoPlz,

@SudoPlz il metodo lo all()fa, attende l'adempimento di tutte le Promesse o il rifiuto di almeno una di esse.
user1016265,

è vero, ma non solo aspetta, in realtà invoca / avvia / attiva il processo. Se desideri accendere le promesse da qualche altra parte che non sarebbe possibile, perché .allaccende tutto.
SudoPlz,

@SudoPlz spero che questo cambi la tua opinione jsfiddle.net/d1z1vey5
user1016265

3
Sono corretto. Fino ad ora pensavo che le Promesse funzionassero solo quando qualcuno le invoca (ovvero una theno una .allchiamata) ma corrono quando vengono create.
SudoPlz,

2

Questo dovrebbe essere coerente con il modo in cui lo fa Q :

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

2

La risposta di Benjamin Gruenbaum è ovviamente eccezionale. Ma vedo anche se il punto di vista di Nathan Hagen con il livello di astrazione sembra vago. Avere proprietà dell'oggetto brevi come e & vnon aiuta neanche, ma ovviamente potrebbe essere cambiato.

In Javascript esiste un oggetto Error standard, chiamato Error,. Idealmente si lancia sempre un'istanza / discendente di questo. Il vantaggio è che puoi fareinstanceof Error e sai che qualcosa è un errore.

Quindi, usando questa idea, ecco la mia opinione sul problema.

In pratica rileva l'errore, se l'errore non è di tipo Errore, avvolgi l'errore all'interno di un oggetto Errore. L'array risultante avrà valori risolti o oggetti Error su cui è possibile verificare.

L'istanza all'interno del fermo è nel caso in cui usi qualche libreria esterna che forse ha fatto reject("error"), invece direject(new Error("error")) .

Ovviamente potresti avere delle promesse se risolvi un errore, ma in quel caso avrebbe molto senso trattare comunque un errore, come mostra l'ultimo esempio.

Un altro vantaggio di farlo, la distruzione dell'array è semplice.

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

Invece di

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

Potresti sostenere che il !error1controllo è più semplice di un'istanza di, ma devi anche distruggere entrambi 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();


2

Invece di rifiutare, risolvilo con un oggetto. Potresti fare qualcosa di simile quando stai promettendo

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


1
Sembra una bella soluzione, non elegante ma funzionerà
Sunny Tambi il

1

Penso che quanto segue offra un approccio leggermente diverso ... confronta fn_fast_fail()con fn_slow_fail()... anche se quest'ultimo non fallisce in quanto tale ... puoi verificare se uno o entrambi aed bè un'istanza di Errore throwche Errorse vuoi che raggiunga il catchblocco (ad es if (b instanceof Error) { throw b; }.). Vedi il 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

0

Ecco la mia abitudine 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;
  });
};

Rispetto a Promise.all

  • Se tutte le promesse vengono risolte, si comporta esattamente come quella standard.

  • Se una o più promesse vengono rifiutate, restituisce la prima respinta più o meno come quella standard, ma a differenza che attende che tutte le promesse vengano risolte / respinte.

Per i coraggiosi potremmo cambiare Promise.all():

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

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

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

ATTENZIONE . In generale non cambiamo mai i built-in, in quanto potrebbero rompere altre librerie JS non correlate o scontrarsi con future modifiche agli standard JS.

My settledPromiseallè retrocompatibile conPromise.all ed estende la sua funzionalità.

Persone che stanno sviluppando standard: perché non includerlo in un nuovo standard Promise?


0

Promise.allcon l'utilizzo di un async/awaitapproccio moderno

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

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

const dataFromPromise1 = data[0]
const dataFromPromise2 = data[1]

-1

Farei:

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

-1

È possibile eseguire la logica in sequenza tramite l'esecutore sincrono nsynjs . Si fermerà su ogni promessa, attenderà la risoluzione / il rifiuto e assegnerà il risultato della risoluzione alla dataproprietà o genererà un'eccezione (per la gestione che sarà necessario provare / bloccare il blocco). Ecco un esempio:

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>


-1

Uso i seguenti codici da 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 firma di utilizzo è proprio come Promise.all. La differenza principale è che Promise.waitaspetteranno che tutte le promesse finiscano il loro lavoro.


-1

So che questa domanda ha molte risposte e sono sicuro che (se non tutte) sono corrette. Tuttavia, è stato molto difficile per me capire la logica / il flusso di queste risposte.

Quindi ho dato un'occhiata all'implementazione originale Promise.all()e ho cercato di imitare quella logica, con l'eccezione di non interrompere l'esecuzione se una Promessa falliva.

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

Spiegazione:
- Passa sopra l'ingresso promisesListed esegui ogni Promessa.
- Non importa se la Promessa è stata risolta o rifiutata: salva il risultato della Promessa in un resultarray secondo la index. Salvare anche lo stato di risoluzione / rifiuto ( isSuccess).
- Una volta completate tutte le promesse, restituisci una promessa con il risultato di tutte le altre.

Esempio di utilizzo:

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

2
Non tentare di reimplementarti Promise.all, ci sono troppe cose che andranno male. Ad esempio, la tua versione non gestisce input vuoti.
Bergi

-4

Non so quale libreria di promesse stai usando, ma la maggior parte ha qualcosa di simile a AllSettled .

Modifica: Ok poiché vuoi usare ES6 semplice senza librerie esterne, non esiste un metodo simile.

In altre parole: devi ripetere manualmente le tue promesse e risolvere una nuova promessa combinata non appena tutte le promesse vengono definite.


Ho chiarito la mia domanda per chiarire: dato che ES6 ha delle promesse, vorrei evitare di usare un'altra libreria per quella che penso sia la funzionalità di base. Immagino che un buon posto per ottenere la risposta sarebbe copiare la fonte da una delle biblioteche promesse.
Nathan Hagen,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.