2017-02-17 9 views
3

По MDN:Почему JavaScript `Promise.all` не выполняет все обещания в условиях отказа?

Если какие-либо из переданных в обещаниях отвергает, то все Promise немедленно отвергает со значением обещания, что отвергнуто, отбрасывая все другие обещания или нет Разрешили они.

ES6 spec, похоже, подтверждает это.

Мой вопрос: Почему Promise.all Отбросить обещает, если любой из них отказаться, так как я ожидал бы ждать «все» обещает уладить, и что именно означает «отбрасывать» означает? (Трудно сказать, что означает «отказаться» от обещаний в полете против обещаний, которые могут еще не выполняться.)

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

const promises = []; // Array of promises 
const settle = promise => promise.then(result => ({ result }), reason => ({ reason })); 
Promise.all(promises.map(settle)) 
    .then(/ * check "reason" property in each element for rejection */); 
+0

Вопросы, которые задают «почему это не спроектировано так, как я хочу» редко (возможно, никогда), не дают удовлетворительного ответа. Ответ почти всегда: «потому что дизайнер думал, что другой способ более полезен». Это было их мнение. У вас есть ваше мнение. Они не всегда будут совпадать. – jfriend00

+0

@ jfriend00 Нет ни одного дизайнера. Спекуляции прибывают на основе консенсуса. Я думаю, что есть веские причины. SO не может быть лучшим местом для автоответчиков, хотя, если вы не захотите прорыть минуты. – jib

+1

Слово «отбросить», используемое в статье MDN, является неудачным. Вероятно, «игнорировать» или «не беспокоиться», или «не делать ничего дальше», было бы лучшим выбором слов. Исправлена. В любом случае, вы говорите * Вместо этого я должен использовать взломать вот так *. Почему вы называете это взломом? –

ответ

5

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

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

Почему Promise.all отказывается от обещаний, если кто-либо из них отвергает, так как я ожидаю, что он будет ждать «всех» обещаний.

Это работает так, как это сделано, потому что именно так оно и было разработано, и это очень распространенный случай использования, когда вы не хотите, чтобы ваш код продолжался, если была какая-либо ошибка. Если это не будет вашим прецедентом, тогда вам нужно использовать некоторую реализацию .settle(), у которой есть поведение, которое вы хотите (что, как вам кажется, уже известно).

Что я нахожу более интересным вопросом, почему нет опции .settle() в спецификации и стандартной реализации, так как это также довольно распространенный вариант использования. К счастью, как вы нашли, кода не так много, чтобы сделать свой собственный. Когда мне не нужно фактической причины отказа и просто хотите некоторое значение индикатора, чтобы быть помещены в массив, я часто использую это довольно простой в использовании версии:

// settle all promises. For rejeted promises, return a specific rejectVal that is 
// distinguishable from your successful return values (often null or 0 or "" or {}) 
Promise.settleVal = function(rejectVal, promises) { 
    return Promise.all(promises.map(function(p) { 
     // make sure any values or foreign promises are wrapped in a promise 
     return Promise.resolve(p).catch(function(err) { 
      // instead of rejection, just return the rejectVal (often null or 0 or "" or {}) 
      return rejectVal; 
     }); 
    })); 
}; 

// sample usage: 
Promise.settleVal(null, someArrayOfPromises).then(function(results) { 
    results.forEach(function(r) { 
     // log successful ones 
     if (r !== null) { 
      console.log(r); 
     } 
    }); 
}); 

что именно означает «отбрасывать» означает?

Это просто означает, что обещания больше не отслеживаются Promise.all(). Асинхронные операции, с которыми они связаны, всегда сохраняют право делать то, что они собираются делать.И на самом деле, если эти обещания имеют на них обработчики .then(), они будут вызваны так, как обычно. discard выглядит как неудачный термин для использования здесь. Ничто не происходит, кроме Promise.all(), перестает обращать на них внимание.


FYI, если я хочу более надежную версию .settle(), которая отслеживает все результаты и отклонять причины, то я использую это:

// ES6 version of settle that returns an instanceof Error for promises that rejected 
Promise.settle = function(promises) { 
    return Promise.all(promises.map(function(p) { 
     // make sure any values or foreign promises are wrapped in a promise 
     return Promise.resolve(p).catch(function(err) { 
      // make sure error is wrapped in Error object so we can reliably detect which promises rejected 
      if (err instanceof Error) { 
       return err; 
      } else { 
       var errObject = new Error(); 
       errObject.rejectErr = err; 
       return errObject; 
      } 
     }); 
    })); 
} 

// usage 
Promise.settle(someArrayOfPromises).then(function(results) { 
    results.forEach(function(r) { 
     if (r instanceof Error) { 
      console.log("reject reason", r.rejectErr); 
     } else { 
      // fulfilled value 
      console.log("fulfilled value:", r); 
     } 
    }); 
}); 

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

+0

Ваш «интересный вопрос» - это то, к чему я пытался добраться, задав свой первоначальный вопрос, аналогичным образом задаваясь вопросом, почему что-то вроде '.allSettle()' не существует на этом языке, поскольку это также похоже на общий прецедент. Я задал вопрос частично, потому что я подумал, что, возможно, есть лучший способ решить этот случай, который мне не хватало. – segfault

+0

@segfault - да, без '.settle()' или '.allSettle()' кажется странным упущением из исходного стандарта.К счастью, это не так много кода, чтобы сделать свой собственный. Библиотека обещаний Bluebird добавила ['.reflect()'] (http://bluebirdjs.com/docs/api/reflect.html), что дает вам доступ к этому типу функций довольно легко, хотя вы все еще кодируете свой собственный ' .settle() '. У Bluebird раньше был '.settle()', но они удалили его по причинам, которые я не знаю. – jfriend00

1

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

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

3

Потому что Promise.all гарантирует, что все они преуспели. Просто как тот.

Это самый полезный строительный блок вместе с Promise.race. На них можно построить все остальное.

Там нет settle, потому что это так тривиально построить like this:

Promise.all([a(), b(), c()].map(p => p.catch(e => e))) 

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