Подождите, пока все обещания ES6 не будут выполнены, даже отклоненные обещания
допустим у меня есть набор обещаний, которые делают сетевые запросы, один из которых будет выполнена:
// 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
не оставляет места для этого, чем рекомендуется шаблон для обработки этого, без использования библиотеки обещаний?
12 ответов:
конечно, вам просто нужно
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: "resolved" }}, 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 === "resolved"); });
аналогичный ответ, но более идиоматичный для 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
для равнины non-object значения,result.message
,result.toString().startsWith("Error:")
etc.)
ответ Бенджамина предлагает отличную абстракцию для решения этой проблемы, но я надеялся на менее абстрактное решение. Явный способ решить эту проблему-просто вызвать
.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 To DRY, когда мне нужно сделать этот компромисс-и именно поэтому я решил не использовать
reflect
.
мне очень нравится ответ Бенджамина, и как он в основном превращает все обещания в Всегда разрешающие, но иногда с ошибкой в результате. :)
Вот моя попытка по вашему запросу на случай, если вы искали альтернативы. Этот метод просто обрабатывает ошибки как допустимые результаты и кодируется аналогично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); }); }
у меня была такая же проблема и я решил ее следующим образом:
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
когда он будет разрешен после тайм-аута.
var err; Promise.all([ promiseOne().catch(function(error) { err = error;}), promiseTwo().catch(function(error) { err = error;}) ]).then(function() { if (err) { throw err; } });
The
Promise.all
проглотит любое отклоненное обещание и сохранит ошибку в переменной, поэтому она вернется, когда все обещания будут разрешены. Затем вы можете повторно выбросить ошибку или сделать что угодно. Таким образом, я думаю, вы получите последний отказ вместо первого.
Это должно быть согласовано с как 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, })))); }; }
вы можете выполнить свою логику последовательно через синхронный исполнитель nsynjs. Он остановится на каждом обещании, дождется разрешения / отклонения и либо назначит результат resolve
data
свойства, или бросить исключение (для обработки, что вам нужно попробовать/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>
Я бы сделал:
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
Я использую следующие коды с 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
будет ждать всех обещаний, чтобы закончить свою работу.
Я не знаю, какую библиотеку обещаний вы используете, но у большинства есть что-то вроде allSettled.
Edit: Ok поскольку вы хотите использовать обычный ES6 без внешних библиотек, такого метода нет.
другими словами: вы должны зациклиться на своих обещаниях вручную и решить новый в сочетании обещайте, как только все обещания будут выполнены.