Подождите, пока все обещания не будут выполнены, даже если некоторые отклонены

405

Допустим, у меня есть набор Promises, которые делают сетевые запросы, один из которых завершится ошибкой:

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

Допустим, я хочу подождать, пока все это не закончится, независимо от того, потерпел ли он неудачу. Может быть сетевая ошибка для ресурса, без которого я могу жить, но который, если я могу получить, я хочу, прежде чем продолжить. Я хочу изящно обрабатывать сбои сети.

Поскольку Promises.allдля этого не остается места, каков рекомендуемый шаблон для обработки этого без использования библиотеки обещаний?

Натан Хаген
источник
Что должно быть возвращено в результирующем массиве для обещаний, которые отклонены?
Куба Уиростек
9
ES6 обещает не поддерживать такой метод (и в настоящее время, по-видимому, медленнее, чем Bluebird ). Кроме того, не все браузеры или движки поддерживают их пока. Я настоятельно рекомендую использовать Bluebird, которая поставляется вместе с тем, чтобы allSettledудовлетворить ваши потребности, без необходимости кататься самостоятельно.
Дэн Кладовая
@KubaWyrostek Я думаю, вы привели причину, по которой Promise.all не имеет такого поведения, что, я думаю, имеет смысл. Это не то, как это работает, но альтернативным вариантом было бы сказать, что Promise.all должен возвращать специальное обещание, которое никогда не завершится неудачей, - и вы получите ошибку, которая была выдана в качестве аргумента, представляющего невыполненное обещание.
Натан Хаген
Чтобы добавить к тому, что поделился Дэн, функциональность allSettled / урегулировать все, что есть у bluebird, можно использовать с помощью функции «отражать».
user3344977 19.09.16
2
@ Коли: Хм, я так не думаю. Promise.allбудет отклонен, как только одно обещание будет отклонено , поэтому предложенная вами идиома не гарантирует, что все обещания будут выполнены.
Йорг Миттаг

Ответы:

310

Обновление, вы, вероятно, хотите использовать встроенный нативный Promise.allSettled:

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

Как забавный факт, этот ответ ниже был предшествующим уровнем техники при добавлении этого метода к языку:]


Конечно, вам просто нужно reflect:

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

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

Или с 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);
});

Или в вашем примере:

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
Я думаю, что это отличное решение. Можете ли вы изменить его, чтобы включить более простой синтаксис? Суть проблемы в том, что если вы хотите обрабатывать ошибки в под-обещаниях, вы должны их перехватить и вернуть ошибку. Так, например: gist.github.com/nhagen/a1d36b39977822c224b8
Натан Хаген
3
@NathanHagen позволяет вам выяснить, что отклонено, а что выполнено, и извлечь проблему оператору многократного использования.
Бенджамин Грюнбаум
4
В ответ на мою собственную проблему я создал следующий пакет npm: github.com/Bucabug/promise-reflect npmjs.com/package/promise-reflect
SamF
2
Я столкнулся с этим вопрос некоторое время назад , и я создал этот пакет НПМ для него: npmjs.com/package/promise-all-soft-fail
velocity_distance
5
Является ли слово reflectраспространенным словом в информатике? Можете ли вы дать ссылку, где это объясняется, как в Википедии или что-то. Я искал трудно, Promise.all not even first rejectно не знал, чтобы искать "Отражение". Должен ли ES6 иметь Promise.reflectчто-то вроде «Promise.all, но на самом деле все»?
Noitidart
253

Подобный ответ, но более идиоматичный для 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>

В зависимости от типа (ов) из возвращаемых значений, ошибки часто можно выделить достаточно легко (например , использование undefinedдля «не важно», typeofдля обычных значений необъектных, result.message, и result.toString().startsWith("Error:")т.д.)

кливер
источник
1
@KarlBateman Я думаю, ты в замешательстве. Функции заказа разрешать или отклонять здесь не имеют значения, так как .map(p => p.catch(e => e))деталь превращает все отклонения в разрешенные значения, поэтому Promise.allвсе еще ожидает завершения всего, независимо от того, сколько времени они принимают, независимо от того, разрешают или отклоняют отдельные функции. Попробуй это.
стрела
39
.catch(e => console.log(e));никогда не вызывается, потому что это никогда не перестает
Фраган
4
@ bfred.it Это правильно. Хотя прекращение цепочки обещаний, catchкак правило, является хорошей практикой ИМХО .
стаксель
2
@SuhailGupta Ловит ошибку eи возвращает ее как обычное (успешное) значение. Так же, как p.catch(function(e) { return e; })только короче. returnнеявно.
стрела
1
@JustinReusnow уже освещен в комментариях. Всегда хорошая практика для завершения цепочек в случае, если вы добавите код позже.
гусек
71

Ответ Бенджамина предлагает отличную абстракцию для решения этой проблемы, но я надеялся на менее абстрактное решение. Явный способ решить эту проблему - просто вызвать .catchвнутренние обещания и вернуть ошибку из их обратного вызова.

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

Сделав еще один шаг вперед, вы можете написать общий обработчик catch, который будет выглядеть так:

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

тогда вы можете сделать

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

Проблема в том, что уловленные значения будут иметь другой интерфейс, чем не уловленные значения, поэтому для очистки вы можете сделать что-то вроде:

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

Так что теперь вы можете сделать это:

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

Затем, чтобы сохранить его сухим, вы получите ответ Бенджамина:

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

где это сейчас выглядит

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

Преимущества второго решения в том, что оно абстрактное и СУХОЕ. Недостатком является то, что у вас есть больше кода, и вы должны помнить, чтобы отражать все ваши обещания, чтобы сделать вещи согласованными.

Я бы охарактеризовал свое решение как явное и ПОЦЕЛУЮ, но на самом деле менее надежное. Интерфейс не гарантирует, что вы точно знаете, было ли обещание выполнено или не выполнено.

Например, у вас может быть это:

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

Это не попадется a.catch, поэтому

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

Невозможно сказать, какой из них был смертельным, а какой нет. Если это важно, то вы захотите применить и интерфейс, который отслеживает, был ли он успешным или нет (что reflectделает).

Если вы просто хотите корректно обрабатывать ошибки, то вы можете просто рассматривать ошибки как неопределенные значения:

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

В моем случае мне не нужно знать об ошибке или о том, как она произошла - мне просто важно, есть ли у меня значение или нет. Я позволю функции, которая генерирует обещание, беспокоиться о регистрации конкретной ошибки.

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

Таким образом, остальная часть приложения может игнорировать свою ошибку, если она хочет, и обрабатывать ее как неопределенное значение, если оно хочет.

Я хочу, чтобы мои высокоуровневые функции благополучно выходили из строя, и не беспокоился о деталях, почему их зависимости перестали работать, и я также предпочитаю, чтобы KISS СУХОЙ, когда я должен сделать этот компромисс - именно поэтому я решил не использовать reflect.

Натан Хаген
источник
1
@ Бенджамин Я думаю, @ решение Натана очень простое и идиоматическое для Promises. Хотя ваш reflectкод улучшает повторное использование, он также устанавливает другой уровень абстракции. Поскольку ответ Натана до сих пор получил лишь небольшую часть голосов против вас, мне интересно, является ли это признаком проблемы с его решением, которую я еще не узнал.
2
@ LUH3417 это решение концептуально менее обоснованно, поскольку оно рассматривает ошибки как значения и не отделяет ошибки от ошибок. Например, если одно из обещаний разрешается на законных основаниях в значение, которое может быть брошено (что вполне возможно), это нарушается довольно плохо.
Бенджамин Грюнбаум
2
@BenjaminGruenbaum Так, например, new Promise((res, rej) => res(new Error('Legitimate error'))не будет отличаться от new Promise(((res, rej) => rej(new Error('Illegitimate error'))? Или, кроме того, вы не сможете отфильтровать x.status? Я добавлю этот момент в свой ответ, чтобы разница стала более ясной
Натан Хаген
3
Причина, по которой это плохая идея, заключается в том, что она связывает реализацию Promise с конкретным вариантом использования, когда-либо использовавшимся только в определенном Promise.all()варианте, а затем потребителю Promise становится известно, что конкретное обещание не будет отклонено, а будет проглотить это ошибки. Фактически, reflect()метод можно сделать менее «абстрактным» и более явным, назвав его PromiseEvery(promises).then(...). Сложность ответа выше по сравнению с ответами Бенджамина должна многое сказать об этом решении.
Нил,
33

Есть законченное предложение для функции, которая может выполнить это изначально, в vanilla Javascript: Promise.allSettledона перешла на этап 4, официально зарегистрирована в ES2020 и реализована во всех современных средах . Это очень похоже на reflectфункцию в этом другом ответе . Вот пример со страницы предложения. Раньше вы должны были сделать:

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

Используя Promise.allSettledвместо этого, выше будет эквивалентно:

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

Те, кто использует современную среду, смогут использовать этот метод без каких-либо библиотек . В них следующий фрагмент должен работать без проблем:

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

Вывод:

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

Для более старых браузеров, есть соответствующая спецификация polyfill здесь .

CertainPerformance
источник
1
Это этап 4 и должен приземлиться в ES2020.
Estus Flask
Также доступный в Узле 12 :)
Callum M
Даже если другие ответы все еще действительны, этот должен получить больше голосов, поскольку это самый актуальный способ решения этой проблемы.
Джейкоб
9

Мне очень нравится ответ Бенджамина, и то, как он в основном превращает все обещания в всегда решающие, но иногда с ошибками, как результат. :)
Вот моя попытка по вашему запросу на тот случай, если вы искали альтернативы. Этот метод просто обрабатывает ошибки как допустимые результаты и кодируется аналогично Promise.allдругому:

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);
  });
}
Куба Уиростек
источник
Это обычно называется settle. У нас это тоже есть в bluebird, мне нравится лучше размышлять, но это жизнеспособное решение для случая, когда у вас есть это для массива.
Бенджамин Грюнбаум
2
ОК, поселиться будет действительно лучшим именем. :)
Куба Уиростек
Это очень похоже на явное обещание конструкции antipattern. Следует отметить, что вам никогда не следует писать такую ​​функцию самостоятельно, а используйте ту, которую поставляет ваша библиотека (хорошо, нативный ES6 немного скуден).
Берги
Не могли бы вы использовать Promiseконструктор правильно (и избегать этой var resolveштуки)?
Берги
Берги, не стесняйтесь изменить ответ, однако вы считаете необходимым.
Куба Уиростек
5
var err;
Promise.all([
    promiseOne().catch(function(error) { err = error;}),
    promiseTwo().catch(function(error) { err = error;})
]).then(function() {
    if (err) {
        throw err;
    }
});

Он Promise.allпроглотит любое отклоненное обещание и сохранит ошибку в переменной, поэтому он вернется, когда все обещания будут разрешены. Затем вы можете повторно выбросить ошибку или сделать что угодно. Таким образом, я полагаю, вы получите последний отказ вместо первого.

martin770
источник
1
Похоже, что это может объединить err.push(error)ошибки, сделав его массивом и использовав его , так что все ошибки могут быть разбиты.
ps2goat
4

У меня была такая же проблема, и я решил ее следующим образом:

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

В этом случае Promise.allбудет ждать, пока каждое Обещание придет resolvedили rejectedзаявит.

И имея это решение, мы «останавливаем catchвыполнение» неблокирующим способом. Фактически, мы ничего не останавливаем, мы просто возвращаем обратно Promiseв состояние ожидания, которое возвращает другое, Promiseкогда оно разрешено после истечения времени ожидания.

user1016265
источник
Но это вызывает все обещания по желанию, когда вы бежите Promise.all. Я ищу способ слушать, когда все обещания были вызваны, но не вызывать их сам. Спасибо.
SudoPlz
@SudoPlz метод all()делает это, он ожидает выполнения всех обещаний или отклонения хотя бы одного из них.
user1016265
это правда, но это не просто ожидание, это фактически вызывает / запускает / запускает процесс. Если вы хотите выполнить обещания где-то еще, что было бы невозможно, потому .allчто все сгорело .
SudoPlz
@SudoPlz надеюсь, что это изменит ваше мнение jsfiddle.net/d1z1vey5
user1016265
3
Я стою исправлено. До сих пор я думал, что Promises выполняются только тогда, когда кто-то вызывает их (aka a thenили .allcall), но они запускаются при создании.
SudoPlz
2

Это должно соответствовать тому, как это делает 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
источник
2

Ответ Бенджамина Грюнбаума, конечно, велик. Но я также вижу, что точка зрения Натана Хагена с уровнем абстракции кажется туманной. Короткие свойства объекта, такие как e & v, также не помогают, но, конечно, это можно изменить.

В Javascript есть стандартный объект Error, называемый Error,. В идеале вы всегда бросаете экземпляр / потомок этого. Преимущество в том, что вы можете сделать instanceof Error, и вы знаете, что-то является ошибкой.

Таким образом, используя эту идею, вот мой взгляд на проблему.

В основном, перехватите ошибку, если ошибка не относится к типу Error, оберните ошибку внутри объекта Error. Полученный массив будет иметь либо разрешенные значения, либо объекты Error, которые вы можете проверить.

Экземпляр внутри улова, в случае, если вы используете внешнюю библиотеку, которая, возможно, сделала reject("error"), вместо reject(new Error("error")).

Конечно, у вас могут быть обещания, если вы разрешите ошибку, но в этом случае, скорее всего, все равно будет иметь смысл рассматривать ошибку, как показано в последнем примере.

Еще одно преимущество этого - уничтожение массива - это просто.

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

Вместо

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

Вы можете утверждать, что !error1проверка проще, чем instanceof, но вам также придется уничтожить и то, и другое 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

Вместо отклонения разрешите его с помощью объекта. Вы могли бы сделать что-то подобное, когда вы выполняете обещание

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
источник
1
Это выглядит неплохо, не элегантно, но будет работать
Sunny Tambi
1

Я думаю , что следующие предложения немного другой подход ... сравнить fn_fast_fail()с fn_slow_fail()... хотя последний не провалится как таковой ... вы можете проверить , если один или оба aи bявляется экземпляром Errorи throwчто , Errorесли вы хотите , чтобы добраться catchблок (например if (b instanceof Error) { throw b; }). См 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
источник
0

Вот мой обычай 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;
  });
};

По сравнению с Promise.all

  • Если все обещания разрешены, он работает точно так же, как стандартный.

  • Если одно из нескольких обещаний отклонено, оно возвращает первое отклоненное, почти то же самое, что и стандартное, но в отличие от этого ждет все обещания, чтобы разрешить / отклонить.

Для смелых мы могли бы изменить Promise.all():

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

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

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

ОСТОРОЖНО . В общем случае мы никогда не меняем встроенные модули, так как это может привести к поломке других не связанных библиотек JS или столкнуться с будущими изменениями стандартов JS.

My settledPromiseallобратно совместим с Promise.allи расширяет его функциональность.

Люди, которые разрабатывают стандарты - почему бы не включить это в новый стандарт Promise?

Эдвард
источник
0

Promise.allс использованием современного async/awaitподхода

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

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

const dataFromPromise1 = data[0]
const dataFromPromise2 = data[1]
Максим Шамихулау
источник
-1

Я бы сделал:

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
источник
-1

Вы можете выполнять свою логику последовательно через синхронного исполнителя nsynjs . Он будет приостанавливать выполнение каждого обещания, ждать разрешения / отклонения и либо присваивать результат разрешения dataсвойству, либо выдавать исключение (для обработки этого вам потребуется блок try / catch). Вот пример:

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
источник
-1

Я использую следующие коды с 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);
                }
            });
        });
    });
};

Подпись использования так же, как Promise.all. Основное отличие состоит в том, что Promise.waitбудут ждать все обещания, чтобы закончить свою работу.

user2273990
источник
-1

Я знаю, что на этот вопрос есть много ответов, и я уверен, что должны (если не все) правильно. Однако мне было очень трудно понять логику / последовательность этих ответов.

Поэтому я посмотрел на Исходную реализацию Promise.all()и попытался имитировать эту логику - за исключением того, что не останавливал выполнение в случае сбоя одного Обещания.

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

Объяснение:
- Зациклите ввод promisesListи выполните каждое обещание.
- Независимо от того, было ли обещание разрешено или отклонено: сохраните результат обещания в resultмассиве в соответствии с index. Сохраните также статус разрешения / отклонения ( isSuccess).
- После выполнения всех обещаний верните одно обещание с результатом всех остальных.

Пример использования:

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
Не пытайтесь повторно реализовать Promise.allсебя, есть слишком много вещей, которые пойдут не так, как надо. Например, ваша версия не обрабатывает пустые данные.
Берги
-4

Я не знаю, какую библиотеку обещаний вы используете, но у большинства есть что-то вроде allSettled .

Изменить: Хорошо, так как вы хотите использовать обычный ES6 без внешних библиотек, такого метода не существует.

Другими словами: вы должны вручную просмотреть свои обещания и разрешить новое объединенное обещание, как только все обещания будут выполнены.

Себастьян С
источник
Я отредактировал свой вопрос, чтобы уточнить: поскольку ES6 поставляется с обещаниями, я бы хотел избежать использования другой библиотеки, которая, на мой взгляд, является базовой функциональностью. Я думаю, что хорошее место, чтобы получить ответ - скопировать исходный код из одной из библиотек обещаний.
Натан Хаген,