Функция аналогична Promise.some/any для неизвестного количества промисов

Я создаю скрипт в node.js (V8.1.3), который просматривает аналогичные данные JSON из нескольких API и сравнивает значения. Точнее, я смотрю на разные рыночные цены разных акций (фактически криптовалют).

В настоящее время я использую promise.all для ожидания всех ответов от соответствующих API.

let fetchedJSON =
        await Promise.all([getJSON(settings1), getJSON(settings2), getJSON(settings3) ... ]); 

Однако Promise.all выдает ошибку, если хотя бы одно обещание отклонено с ошибкой. В документах bluebird есть функция под названием Promise.some, и это почти то, что я хочу. Насколько я понимаю, он берет массив обещаний и разрешает два самых быстрых обещания для разрешения, иначе (если разрешается менее 2 обещаний) выдает ошибку.

Проблема заключается в том, что, во-первых, я не хочу, чтобы два самых быстрых промиса разрешались как то, что он возвращает, я хочу, чтобы возвращались все успешные промисы, если их больше 2. Это Кажется, это то, что делает Promise.any, за исключением минимального количества 1. (Мне требуется минимальное количество 2)

Во-вторых, я не знаю, сколько промисов я буду ожидать (другими словами, я не знаю, из скольких API я буду запрашивать данные). Это может быть только 2 или может быть 30. Это зависит от ввода пользователя.

В настоящее время я пишу это, и мне кажется, что, вероятно, есть способ получить promise.any со счетом 2, и это было бы самым простым решением. Это возможно?

Кстати, не уверен, что заголовок действительно резюмирует вопрос. Пожалуйста, предложите правку для заголовка :)

РЕДАКТИРОВАТЬ: Другой способ, которым я могу написать сценарий, заключается в том, что первые два загружаемых API начинают вычисляться и передаются в браузер, а затем каждый следующий JSON, который загружается и вычисляется после него. Таким образом, я не жду, пока все промисы будут выполнены, прежде чем я начну вычислять данные и передавать результаты во внешний интерфейс. Возможно ли это с помощью функции, которая работает и при других обстоятельствах?

То, что я имею в виду, выглядит примерно так:

Параллельный запрос JSON...

|-----JSON1------|

|---JSON-НЕУДАЧА---| > поймать ошибку > сделать что-то с ошибкой. Не влияет на следующие результаты.

|-------JSON2-------| > Соответствует минимум 2 результатам > вычисляет JSON > для браузера.

|-------JSON3---------| > вычисляет JSON > в браузер.


person Manu    schedule 30.06.2017    source источник
comment
Можете ли вы просто .forEach() сгенерировать массив обещаний и .then() каждое из них по отдельности?   -  person Patrick Roberts    schedule 30.06.2017


Ответы (4)


Как насчет thenобработки всех обещаний, чтобы ни одно из них не провалилось, передать это Promise.all и отфильтровать успешные результаты в окончательном .then.

Что-то вроде этого:

function some( promises, count = 1 ){

   const wrapped = promises.map( promise => promise.then(value => ({ success: true, value }), () => ({ success: false })) );
   return Promise.all( wrapped ).then(function(results){
      const successful = results.filter(result => result.success);
      if( successful.length < count )
         throw new Error("Only " + successful.length + " resolved.")
      return successful.map(result => result.value);
   });

}
person pishpish    schedule 30.06.2017

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

async function fetchAllJSON(settingsArray) {
  let fetchedJSON = await Promise.all(
    settingsArray.map((settings) => {
      // force rejected ajax to always resolve
      return getJSON(settings).then((data) => {
        // initial processing
        return { success: true, data }
      }).catch((error) => {
        // error handling
        return { success, false, error }
      })
    })
  ).then((unfilteredArray) => {
    // only keep successful promises
    return dataArray.filter(({ success }) => success)
  })
  // do the rest of your processing here
  // with fetchedJSON containing array of data
}
person Patrick Roberts    schedule 30.06.2017
comment
Я исправил то, что, по моему мнению, могло быть причиной отрицательного голоса. Проблема заключалась в упаковке с new Promise(...)? - person Patrick Roberts; 30.06.2017
comment
разве это не должно быть let fetchedJSON = await Promise.all(... ? - person Tamas Hegedus; 30.06.2017
comment
@TamasHegedus да, спасибо, что поймал, не видел. - person Patrick Roberts; 30.06.2017

Вы можете использовать Promise.allSettled([]). разница в том, что allSettled вернет массив объектов после того, как все промисы будут выполнены, независимо от того, успешны они или нет. затем просто найдите успешный o все, что вам нужно.

  let resArr = await Promise.allSettled(userNamesArr.map(user=>this.authenticateUserPassword(user,password)));
        return resArr.find(e=>e.status!="rejected");

ИЛИ вернуть resArr.find(e=>e.status=="выполнено").

person Nathan Borik    schedule 30.05.2020

Недостатком других ответов является необходимость ждать разрешения всех промисов, тогда как в идеале .some вернется, как только любое (N) промисов передаст предикат.

let anyNPromises = (promises, predicate = a => a, n = 1) => new Promise(async resolve => {
	promises.forEach(async p => predicate(await p) && !--n && resolve(true));
	await Promise.all(promises);
	resolve(false);
});

let atLeast2NumbersGreaterThan5 = promises => anyNPromises(promises, a => a > 5, 2);

atLeast2NumbersGreaterThan5([
	Promise.resolve(5),
	Promise.resolve(3),
	Promise.resolve(10),
	Promise.resolve(11)]
).then(a => console.log('5, 3, 10, 11', a)); // true

atLeast2NumbersGreaterThan5([
	Promise.resolve(5),
	Promise.resolve(3),
	Promise.resolve(10),
	Promise.resolve(-43)]
).then(a => console.log('5, 3, 10, -43', a)); // false

atLeast2NumbersGreaterThan5([
	Promise.resolve(5),
	Promise.resolve(3),
	new Promise(() => 'never resolved'),
	Promise.resolve(10),
	Promise.resolve(11)]
).then(a => console.log('5, 3, unresolved, 10, 11', a)); // true

person junvar    schedule 16.10.2019