Espere hasta que todas las promesas se completen, incluso si algunas rechazadas

405

Digamos que tengo un conjunto de Promisecorreos electrónicos que están haciendo solicitudes de red, de los cuales uno fallará:

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

Digamos que quiero esperar hasta que todo esto haya terminado, independientemente de si uno ha fallado. Puede haber un error de red para un recurso sin el cual puedo vivir, pero que si puedo obtener, quiero antes de continuar. Quiero manejar fallas de red con gracia.

Como Promises.allno deja espacio para esto, ¿cuál es el patrón recomendado para manejar esto, sin usar una biblioteca de promesas?

Nathan Hagen
fuente
¿Qué debe devolverse en la matriz resultante para las promesas rechazadas?
Kuba Wyrostek
99
ES6 promete no admitir dicho método (y actualmente son aparentemente más lentos que Bluebird ). Además, no todos los navegadores o motores los admiten todavía. Recomiendo encarecidamente el uso de Bluebird, que viene con el allSettledque satisface su necesidad sin que tenga que rodar la suya.
Dan Pantry
@KubaWyrostek Creo que mencionas la razón por la que Promise.all no tiene este comportamiento, lo que creo que tiene sentido. Así no es como funciona, pero una visión alternativa sería decir Promesa. Todos deberían devolver una promesa especial que nunca falla, y obtendría el error que se arrojó como el argumento que representa la promesa fallida.
Nathan Hagen
Para agregar a lo que Dan compartió, la funcionalidad allSettled / resolveAll like que tiene bluebird se puede utilizar a través de la función "reflejar".
user3344977
2
@ Coli: Hmm, no lo creo. Promise.allrechazará tan pronto como cualquier promesa lo rechace, por lo que su modismo propuesto no garantiza que todas las promesas se resuelvan.
Jörg W Mittag

Respuestas:

310

Actualización, probablemente desee utilizar el nativo incorporado Promise.allSettled:

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

Como dato curioso, esta respuesta a continuación fue una técnica anterior al agregar ese método al lenguaje:]


Claro, solo necesitas 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 en tu ejemplo:

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
fuente
3
Creo que esta es una gran solución. ¿Se puede modificar para incluir una sintaxis más simple? El quid de la cuestión es que si desea manejar errores en sub-promesas, debe atraparlos y devolver el error. Entonces, por ejemplo: gist.github.com/nhagen/a1d36b39977822c224b8
Nathan Hagen
3
@NathanHagen te permite descubrir qué rechazó y qué cumplió y extrae el problema a un operador reutilizable.
Benjamin Gruenbaum
44
En respuesta a mi propio problema, he creado el siguiente paquete npm : github.com/Bucabug/promise-reflect npmjs.com/package/promise-reflect
SamF
2
Me encontré con este problema hace un tiempo y creé este paquete npm para él: npmjs.com/package/promise-all-soft-fail
velocity_distance
55
¿Es la palabra reflectuna palabra común en informática? ¿Puedes vincular a donde se explica esto como en wikipedia o algo así? Estaba buscando mucho Promise.all not even first rejectpero no sabía buscar "Reflexionar". ¿Debería ES6 tener un Promise.reflectque sea como "Promise.all pero realmente todos"?
Noitidart
253

Respuesta similar, pero más idiomática para ES6 quizás:

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>

Dependiendo del tipo o tipos de valores devueltos, los errores a menudo se pueden distinguir con la suficiente facilidad (por ejemplo, usar undefinedpara "no importa", typeofpara valores simples que no sean objetos result.message, result.toString().startsWith("Error:")etc.)

foque
fuente
1
@KarlBateman Creo que estás confundido. Las funciones de orden se resuelven o rechazan aquí, no importa, ya que la .map(p => p.catch(e => e))parte convierte todos los rechazos en valores resueltos, por lo que Promise.allaún espera a que todo termine si las funciones individuales se resuelven o rechazan, independientemente del tiempo que tarden. Intentalo.
foque
39
.catch(e => console.log(e));nunca se llama porque esto nunca falla
fregante
44
@ bfred.it Eso es correcto. Aunque terminar las cadenas de promesa catches generalmente una buena práctica en mi humilde opinión .
foque
2
@SuhailGupta Captura el error ey lo devuelve como un valor regular ( correcto ). Igual que p.catch(function(e) { return e; })solo más corto. returnEs implícito.
foque
1
@JustinReusnow ya está cubierto en comentarios. Siempre es una buena práctica terminar las cadenas en caso de que agregue código más tarde.
foque
71

La respuesta de Benjamin ofrece una gran abstracción para resolver este problema, pero esperaba una solución menos abstracta. La forma explícita de resolver este problema es simplemente recurrir .catcha las promesas internas y devolver el error de su devolución de llamada.

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

Dando un paso más allá, podría escribir un controlador genérico de captura que se vea así:

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

entonces puedes hacer

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

El problema con esto es que los valores capturados tendrán una interfaz diferente a los valores no capturados, por lo que para limpiar esto puede hacer algo como:

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

Entonces ahora puedes hacer esto:

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

Luego, para mantenerlo SECO, obtienes la respuesta de Benjamin:

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

donde ahora parece

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

Los beneficios de la segunda solución son su abstracción y SECO. La desventaja es que tiene más código y debe recordar reflejar todas sus promesas para hacer que las cosas sean consistentes.

Caracterizaría mi solución como explícita y KISS, pero de hecho menos robusta. La interfaz no garantiza que sepa exactamente si la promesa tuvo éxito o no.

Por ejemplo, puede tener esto:

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

Esto no será atrapado a.catch, así que

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

No hay forma de saber cuál fue fatal y cuál no. Si eso es importante, entonces querrá aplicar una interfaz que rastree si fue exitosa o no (lo que reflectsí ocurre).

Si solo desea manejar los errores con gracia, puede tratar los errores como valores indefinidos:

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

En mi caso, no necesito saber el error o cómo falló, solo me importa si tengo el valor o no. Dejaré que la función que genera la promesa se preocupe por registrar el error específico.

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

De esa manera, el resto de la aplicación puede ignorar su error si lo desea, y tratarlo como un valor indefinido si lo desea.

Quiero que mis funciones de alto nivel fallen con seguridad y no me preocupe por los detalles de por qué fallaron sus dependencias, y también prefiero KISS a DRY cuando tengo que hacer ese intercambio, que es en última instancia por lo que opté por no usarlo reflect.

Nathan Hagen
fuente
1
@Benjamin Creo que la solución de @ Nathan es muy directa e idiomática para Promises. Si bien reflectmejora la reutilización del código, también establece otro nivel de abstracción. Como la respuesta de Nathan hasta ahora solo ha recibido una fracción de votos positivos en comparación con la suya, me pregunto si esto es una indicación de un problema con su solución, que aún no he reconocido.
2
@ LUH3417 esta solución es conceptualmente menos sólida ya que trata los errores como valores y no separa los errores de los que no son errores. Por ejemplo, si una de las promesas se resuelve legítimamente a un valor que puede arrojarse (lo cual es totalmente posible), esto se rompe bastante mal.
Benjamin Gruenbaum
2
@BenjaminGruenbaum Entonces, por ejemplo, new Promise((res, rej) => res(new Error('Legitimate error'))¿no sería distinguible de new Promise(((res, rej) => rej(new Error('Illegitimate error'))? O, además, ¿no podrías filtrar x.status? Agregaré este punto a mi respuesta para que la diferencia sea más clara
Nathan Hagen el
3
La razón por la que esta es una mala idea es porque vincula la implementación de Promise a un caso de uso específico de solo ser utilizado en una Promise.all()variante específica , y luego incumbe al consumidor de Promise saber que una promesa específica no rechazará, pero tragar es errores. De hecho, el reflect()método podría hacerse menos "abstracto" y más explícito llamándolo PromiseEvery(promises).then(...). La complejidad de la respuesta anterior en comparación con la de Benjamin debería decir mucho sobre esta solución.
Neil
33

Hay una propuesta final para una función que puede lograr esto de forma nativa, en Javascript vainilla: Promise.allSettledque ha llegado a la etapa 4, está oficializada en ES2020 y se implementa en todos los entornos modernos . Es muy similar a la reflectfunción en esta otra respuesta . Aquí hay un ejemplo, de la página de propuesta. Antes, habrías tenido que hacer:

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

Usando en su Promise.allSettledlugar, lo anterior será 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');

Aquellos que usan entornos modernos podrán usar este método sin ninguna biblioteca . En esos, el siguiente fragmento debe ejecutarse sin problemas:

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

Salida:

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

Para los navegadores más antiguos, hay una especificación polyfill compatible aquí .

Cierto rendimiento
fuente
1
Es la etapa 4 y se supone que aterrizará en ES2020.
Estus Flask
También disponible en el Nodo 12 :)
Callum M
Incluso si las otras respuestas siguen siendo válidas, esta debería obtener más votos positivos, ya que es la forma más actual de resolver este problema.
Jacob
9

Realmente me gusta la respuesta de Benjamin, y cómo él básicamente convierte todas las promesas en siempre resueltas, pero a veces con errores como resultado. :)
Aquí está mi intento de su solicitud en caso de que estuviera buscando alternativas. Este método simplemente trata los errores como resultados válidos y se codifica de forma similar a lo 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);
  });
}
Kuba Wyrostek
fuente
Esto se llama típicamente settle. También tenemos eso en bluebird, me gusta reflexionar mejor, pero esta es una solución viable para cuando tienes esto para una matriz.
Benjamin Gruenbaum
2
OK, resolver será un mejor nombre de hecho. :)
Kuba Wyrostek
Esto se parece mucho a la promesa explícita de construcción antipatrón. Debe tenerse en cuenta que nunca debe escribir dicha función usted mismo, pero use la que proporciona su biblioteca (OK, ES6 nativo es un poco escaso).
Bergi
¿Podría usar el Promiseconstructor correctamente (y evitar esa var resolvecosita)?
Bergi
Bergi, siéntete libre de alterar la respuesta como consideres necesario.
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;
    }
});

Se Promise.alltragará cualquier promesa rechazada y almacenará el error en una variable, por lo que volverá cuando se hayan resuelto todas las promesas. Luego puede volver a tirar el error o hacer lo que sea. De esta manera, supongo que sacarías el último rechazo en lugar del primero.

martin770
fuente
1
Parece que esto podría agregar errores al convertirlo en una matriz y usarlo err.push(error), por lo que todos los errores podrían aparecer.
ps2goat
4

Tuve el mismo problema y lo resolví de la siguiente manera:

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

En ese caso Promise.allesperará a que se cumplan todas las Promesas resolvedo rejectedestados.

Y teniendo esta solución, estamos "deteniendo la catchejecución" de una manera no bloqueante. De hecho, no detenemos nada, solo regresamos al Promiseestado pendiente que devuelve otro Promisecuando se resuelve después del tiempo de espera.

usuario1016265
fuente
Pero eso invoca todas las promesas a voluntad cuando corres Promise.all. Estoy buscando una forma de escuchar cuando se han invocado todas las promesas, pero no invocarlas yo mismo. Gracias.
SudoPlz
@SudoPlz el método all()hace eso, espera el cumplimiento de todas las promesas o el rechazo de al menos una de ellas.
user1016265
eso es cierto, pero no solo espera, sino que invoca / inicia / activa el proceso. Si desea poner en marcha las promesas en otro lugar que no sería posible, porque .alltodo se dispara.
SudoPlz
@SudoPlz espero que esto cambie su opinión jsfiddle.net/d1z1vey5
user1016265
3
Estoy corregido. Hasta ahora pensé que las promesas solo se ejecutan cuando alguien las invoca (también conocido como una theno una .allllamada) pero se ejecutan cuando se crean.
SudoPlz
2

Esto debería ser coherente con cómo lo hace 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,
        }))));
    };
}
mpen
fuente
2

La respuesta de Benjamin Gruenbaum es, por supuesto, genial. Pero también puedo ver si el punto de vista de Nathan Hagen con el nivel de abstracción parece vago. Tener propiedades de objeto cortas como e & vtampoco ayuda, pero por supuesto eso podría cambiarse.

En Javascript hay un objeto de error estándar, llamado Error,. Idealmente, siempre arrojas una instancia / descendiente de esto. La ventaja es que puedes hacerinstanceof Error , y sabes que algo es un error.

Entonces, usando esta idea, aquí está mi opinión sobre el problema.

Básicamente, detecte el error, si el error no es del tipo Error, ajuste el error dentro de un objeto Error. La matriz resultante tendrá valores resueltos u objetos de error que puede verificar.

La instancia de dentro de la captura, es en caso de que use alguna biblioteca externa que tal vez lo hizo reject("error"), en lugar de reject(new Error("error")).

Por supuesto, podría tener promesas si resuelve un error, pero en ese caso lo más probable es que tenga sentido tratarlo como un error, como muestra el último ejemplo.

Otra ventaja de hacerlo es que la destrucción de la matriz se mantiene simple.

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

En vez de

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

Podría argumentar que la !error1verificación es más simple que una instancia de, pero también tiene que destruir ambos 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
fuente
2

En lugar de rechazar, resuélvelo con un objeto. Podrías hacer algo como esto cuando estás implementando promesa

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
fuente
1
Parece un buen trabajo, no elegante pero funcionará
Sunny Tambi
1

Creo que los siguientes ofrece un enfoque ligeramente diferente ... Comparar fn_fast_fail()con fn_slow_fail()... aunque este último no falla como tal ... se puede comprobar si una o las dos ay bes una instancia de Errory throwque Errorsi quiero que se alcance El catchbloque (por ejemplo if (b instanceof Error) { throw b; }). Ver el 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
fuente
0

Aquí está mi costumbre 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;
  });
};

Comparado con Promise.all

  • Si se resuelven todas las promesas, funciona exactamente como la estándar.

  • Si se rechaza una o más promesas, devuelve la primera rechazada de manera muy similar a la estándar pero, a diferencia de esto, espera que todas las promesas se resuelvan / rechacen.

Para los valientes podríamos cambiar Promise.all():

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

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

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

CUIDADO . En general, nunca cambiamos los elementos integrados, ya que podría romper otras bibliotecas JS no relacionadas o chocar con futuros cambios en los estándares JS.

My settledPromisealles compatible con versiones anteriores Promise.ally extiende su funcionalidad.

Personas que están desarrollando estándares: ¿por qué no incluir esto en un nuevo estándar de Promise?

Eduardo
fuente
0

Promise.allcon el uso de un async/awaitenfoque moderno

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

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

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

Yo lo haría:

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
fuente
-1

Puede ejecutar su lógica secuencialmente a través del ejecutor síncrono nsynjs . Pausará cada promesa, esperará la resolución / rechazo y asignará el resultado de la resolución a la datapropiedad, o lanzará una excepción (para el manejo que necesitará try / catch block). Aquí hay un ejemplo:

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
fuente
-1

He estado usando los siguientes códigos desde 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 de uso es igual Promise.all. La principal diferencia es que Promise.waitesperará a que todas las promesas terminen sus trabajos.

usuario2273990
fuente
-1

Sé que esta pregunta tiene muchas respuestas, y estoy seguro de que (si no todas) deben ser correctas. Sin embargo, fue muy difícil para mí entender la lógica / flujo de estas respuestas.

Así que miré la Implementación original Promise.all()e intenté imitar esa lógica, con la excepción de no detener la ejecución si fallaba una Promesa.

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

Explicación:
- Pase sobre la entrada promisesListy ejecute cada Promesa.
- No importa si la Promesa se resolvió o rechazó: guarde el resultado de la Promesa en una resultmatriz de acuerdo con index. Guarde también el estado de resolución / rechazo ( isSuccess).
- Una vez completadas todas las promesas, devuelve una promesa con el resultado de todas las demás.

Ejemplo de uso:

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
fuente
2
No intentes volver a implementarte Promise.all, hay muchas cosas que saldrán mal. Su versión no maneja entradas vacías, por ejemplo.
Bergi
-4

No sé qué biblioteca de promesas está utilizando, pero la mayoría tiene algo como allSettled .

Editar: Ok, ya que desea usar ES6 simple sin bibliotecas externas, no existe tal método.

En otras palabras: debe realizar un bucle de sus promesas manualmente y resolver una nueva promesa combinada tan pronto como se resuelvan todas las promesas.

Sebastian S
fuente
He editado mi pregunta para aclarar: dado que ES6 viene con promesas, me gustaría evitar usar otra biblioteca para lo que creo que es la funcionalidad básica. Supongo que un buen lugar para obtener la respuesta sería copiar la fuente de una de las bibliotecas de promesa.
Nathan Hagen