Разорвать цепочку обещаний и вызвать функцию на основе шага в цепочке, где она нарушена (отклонена)
обновление:
, чтобы помочь будущим зрителям этого поста, я создал эта демонстрация ответа pluma.
вопрос:
моя цель кажется довольно простой.
step(1)
.then(function() {
return step(2);
}, function() {
stepError(1);
return $q.reject();
})
.then(function() {
}, function() {
stepError(2);
});
function step(n) {
var deferred = $q.defer();
//fail on step 1
(n === 1) ? deferred.reject() : deferred.resolve();
return deferred.promise;
}
function stepError(n) {
console.log(n);
}
проблема здесь в том, что если я потерплю неудачу на шаге 1, оба stepError(1)
и stepError(2)
уволен. Если я этого не сделаю return $q.reject
затем stepError(2)
не будет уволен, но step(2)
будет, как я понимаю. Я достиг всего, кроме того, что я пытаюсь делать.
Как написать обещания, чтобы я мог вызвать функцию при отклонении, не вызывая все функции в цепочке ошибок? Или есть другой способ сделать это?
вот демо так у тебя есть с чем работать.
обновление:
я вид ее решили. Здесь я ловлю ошибку в конце цепочки и передаю данные в reject(data)
так что я буду знать, что проблема в функции ошибки. Это на самом деле не соответствует моим требованиям, потому что я не хочу зависеть от данных. Это было бы хромым, но в моем случае было бы чище передать обратный вызов ошибки функции, а не зависеть от возвращенных данных, чтобы определить, что делать.
step(1)
.then(function() {
return step(2);
})
.then(function() {
return step(3);
})
.then(false,
function(x) {
stepError(x);
}
);
function step(n) {
console.log('Step '+n);
var deferred = $q.defer();
(n === 1) ? deferred.reject(n) : deferred.resolve(n);
return deferred.promise;
}
function stepError(n) {
console.log('Error '+n);
}
9 ответов:
причина, по которой ваш код не работает так, как ожидалось, заключается в том, что он на самом деле делает что-то другое, чем вы думаете.
допустим, у вас есть что-то вроде следующего:
stepOne() .then(stepTwo, handleErrorOne) .then(stepThree, handleErrorTwo) .then(null, handleErrorThree);
чтобы лучше понять, что происходит, давайте представим, что это синхронный код
try
/catch
блоки:try { try { try { var a = stepOne(); } catch(e1) { a = handleErrorOne(e1); } var b = stepTwo(a); } catch(e2) { b = handleErrorTwo(e2); } var c = stepThree(b); } catch(e3) { c = handleErrorThree(e3); }
The
onRejected
обработчик (второй аргументthen
) по существу является механизмом исправления ошибок (например,catch
блок). Если ошибка выдается вhandleErrorOne
, он будет пойман следующим блоком catch (catch(e2)
), и так далее.это явно не то, что вы имели в виду.
предположим, что мы хотим, чтобы вся цепочка разрешения потерпела неудачу независимо от того, что идет не так:
stepOne() .then(function(a) { return stepTwo(a).then(null, handleErrorTwo); }, handleErrorOne) .then(function(b) { return stepThree(b).then(null, handleErrorThree); });
Примечание: мы можем оставить
handleErrorOne
где он находится, потому что он будет вызван только еслиstepOne
отклоняет (это первая функция в цепочке, поэтому мы знаем, что если цепь отклонена в этот момент, она может быть только из-за обещания этой функции).важным изменением является то, что обработчики ошибок для других функций не являются частью основной цепочки обещаний. Вместо этого каждый шаг имеет свою собственную "подцепку" с
onRejected
это вызывается только в том случае, если шаг был отклонен (но не может быть достигнут непосредственно основной цепью).причина, по которой это работает, заключается в том, что оба
onFulfilled
иonRejected
являются необязательными аргументами дляthen
метод. Если обещание выполнено (т. е. разрешено) и следующийthen
в сети нетonFulfilled
обработчик, цепочка будет продолжаться до тех пор, пока не появится один с таким обработчиком.это означает, что следующие две строки эквивалентны:
stepOne().then(stepTwo, handleErrorOne) stepOne().then(null, handleErrorOne).then(stepTwo)
но следующая строка не эквивалентно двум выше:
stepOne().then(stepTwo).then(null, handleErrorOne)
библиотека обещаний Angular
$q
основан на kriskowal'sQ
библиотека (которая имеет более богатый API, но содержит все, что вы можете найти в$q
). Вопрос по API docs на GitHub может оказаться полезным. Вопрос реализует обещания/A + spec, который подробно рассказывает о том, какthen
и поведение разрешения обещания работает точно.EDIT:
также имейте в виду, что если вы хотите вырваться из цепочки в своем обработчике ошибок, он должен вернуть отклоненное обещание или бросить ошибку (которая будет поймана и обернута в отклоненное обещание автоматически). Если ты не вернешь обещание,
then
обертывает возвращаемое значение в обещание разрешения для вас.это означает, что если вы ничего не возвращаете, вы фактически возвращаете разрешенное обещание для значения
undefined
.
немного опоздала на вечеринку, но это простое решение сработало для меня:
function chainError(err) { return Promise.reject(err) }; stepOne() .then(stepTwo, chainError) .then(stepThreee, chainError);
Это позволяет перерыв из цепи.
то, что вам нужно, это повторение
.then()
цепь со специальным случаем для начала и специальным случаем для завершения.ловкость заключается в том, чтобы получить номер шага случая сбоя для пульсации до конечного обработчика ошибок.
- Start: call
step(1)
безоговорочно.- повторяющийся шаблон: цепь a
.then()
со следующими ответами:
- успех: вызов шаг (n+1)
- failure: бросьте значение, с которым предыдущий отложенный был отклонен или переосмыслен ошибка.
- отделка: цепь a
.then()
без обработчика успеха и окончательного обработчика ошибок.вы можете написать все это от руки, но легче продемонстрировать шаблон с именованными, обобщенными функциями:
function nextStep(n) { return step(n + 1); } function step(n) { console.log('step ' + n); var deferred = $q.defer(); (n === 3) ? deferred.reject(n) : deferred.resolve(n); return deferred.promise; } function stepError(n) { throw(n); } function finalError(n) { console.log('finalError ' + n); } step(1) .then(nextStep, stepError) .then(nextStep, stepError) .then(nextStep, stepError) .then(nextStep, stepError) .then(nextStep, stepError) .then(null, finalError);});
посмотреть демо
обратите внимание, как в
step()
, отложенное отклоняется или разрешается с помощьюn
, таким образом, делая это значение доступно обратные вызовы в следующем.then()
в цепи. Один разstepError
вызывается, ошибка повторно перестраивается, пока она не будет обработанаfinalError
.
при отклонении вы должны передать ошибку отклонения, а затем обернуть обработчики ошибок шага в функцию, которая проверяет, следует ли обрабатывать отклонение или" перестраивать " до конца цепочки:
// function mocking steps function step(i) { i++; console.log('step', i); return q.resolve(i); } // function mocking a failing step function failingStep(i) { i++; console.log('step '+ i + ' (will fail)'); var e = new Error('Failed on step ' + i); e.step = i; return q.reject(e); } // error handler function handleError(e){ if (error.breakChain) { // handleError has already been called on this error // (see code bellow) log('errorHandler: skip handling'); return q.reject(error); } // firs time this error is past to the handler console.error('errorHandler: caught error ' + error.message); // process the error // ... // error.breakChain = true; return q.reject(error); } // run the steps, will fail on step 4 // and not run step 5 and 6 // note that handleError of step 5 will be called // but since we use that error.breakChain boolean // no processing will happen and the error will // continue through the rejection path until done(,) step(0) // 1 .catch(handleError) .then(step) // 2 .catch(handleError) .then(step) // 3 .catch(handleError) .then(failingStep) // 4 fail .catch(handleError) .then(step) // 5 .catch(handleError) .then(step) // 6 .catch(handleError) .done(function(){ log('success arguments', arguments); }, function (error) { log('Done, chain broke at step ' + error.step); });
что вы увидите в консоли :
step 1 step 2 step 3 step 4 (will fail) errorHandler: caught error 'Failed on step 4' errorHandler: skip handling errorHandler: skip handling Done, chain broke at step 4
вот рабочий код https://jsfiddle.net/8hzg5s7m/3/
если у вас есть определенная обработка для каждого шага, ваша обертка может быть чем-то вроде:
/* * simple wrapper to check if rejection * has already been handled * @param function real error handler */ function createHandler(realHandler) { return function(error) { if (error.breakChain) { return q.reject(error); } realHandler(error); error.breakChain = true; return q.reject(error); } }
затем ваша цепь
step1() .catch(createHandler(handleError1Fn)) .then(step2) .catch(createHandler(handleError2Fn)) .then(step3) .catch(createHandler(handleError3Fn)) .done(function(){ log('success'); }, function (error) { log('Done, chain broke at step ' + error.step); });
Если я правильно понимаю, вы хотите, чтобы только ошибка для неудачного шага, чтобы показать, не так ли?
Это должно быть так же просто, как изменить случай отказа Первого обещания на это:
step(1).then(function (response) { step(2); }, function (response) { stepError(1); return response; }).then( ... )
вернуться
$q.reject()
в случае неудачи первого шага вы отвергаете это обещание, что приводит к вызову errorCallback во 2-мthen(...)
.
var s = 1; start() .then(function(){ return step(s++); }) .then(function() { return step(s++); }) .then(function() { return step(s++); }) .then(0, function(e){ console.log(s-1); });
http://jsbin.com/EpaZIsIp/20/edit
или автоматизировано для любого количества шагов:
var promise = start(); var s = 1; var l = 3; while(l--) { promise = promise.then(function() { return step(s++); }); } promise.then(0, function(e){ console.log(s-1); });
прикрепите обработчики ошибок в виде отдельных элементов цепочки непосредственно к выполнению шагов:
// Handle errors for step(1) step(1).then(null, function() { stepError(1); return $q.reject(); }) .then(function() { // Attach error handler for step(2), // but only if step(2) is actually executed return step(2).then(null, function() { stepError(2); return $q.reject(); }); }) .then(function() { // Attach error handler for step(3), // but only if step(3) is actually executed return step(3).then(null, function() { stepError(3); return $q.reject(); }); });
или через
catch()
:// Handle errors for step(1) step(1).catch(function() { stepError(1); return $q.reject(); }) .then(function() { // Attach error handler for step(2), // but only if step(2) is actually executed return step(2).catch(function() { stepError(2); return $q.reject(); }); }) .then(function() { // Attach error handler for step(3), // but only if step(3) is actually executed return step(3).catch(function() { stepError(3); return $q.reject(); }); });
Примечание: это в основном тот же шаблон, что и плума предлагает в своем ответе но с использованием именования OP.
нашел
Promise.prototype.catch()
примеры на MDN ниже очень полезная.(принятый ответ упоминает
then(null, onErrorHandler)
что в основном то же самое, чтоcatch(onErrorHandler)
.)использование и цепочка метода catch
var p1 = new Promise(function(resolve, reject) { resolve('Success'); }); p1.then(function(value) { console.log(value); // "Success!" throw 'oh, no!'; }).catch(function(e) { console.log(e); // "oh, no!" }).then(function(){ console.log('after a catch the chain is restored'); }, function () { console.log('Not fired due to the catch'); }); // The following behaves the same as above p1.then(function(value) { console.log(value); // "Success!" return Promise.reject('oh, no!'); }).catch(function(e) { console.log(e); // "oh, no!" }).then(function(){ console.log('after a catch the chain is restored'); }, function () { console.log('Not fired due to the catch'); });
Gotchas при выбрасывании ошибок
// Throwing an error will call the catch method most of the time var p1 = new Promise(function(resolve, reject) { throw 'Uh-oh!'; }); p1.catch(function(e) { console.log(e); // "Uh-oh!" }); // Errors thrown inside asynchronous functions will act like uncaught errors var p2 = new Promise(function(resolve, reject) { setTimeout(function() { throw 'Uncaught Exception!'; }, 1000); }); p2.catch(function(e) { console.log(e); // This is never called }); // Errors thrown after resolve is called will be silenced var p3 = new Promise(function(resolve, reject) { resolve(); throw 'Silenced Exception!'; }); p3.catch(function(e) { console.log(e); // This is never called });
если он разрешен
//Create a promise which would not call onReject var p1 = Promise.resolve("calling next"); var p2 = p1.catch(function (reason) { //This is never called console.log("catch p1!"); console.log(reason); }); p2.then(function (value) { console.log("next promise's onFulfilled"); /* next promise's onFulfilled */ console.log(value); /* calling next */ }, function (reason) { console.log("next promise's onRejected"); console.log(reason); });