Вырваться из обещания, а затем связать с errorCallback

-- РЕДАКТИРОВАТЬ --

Недавно я столкнулся со странной вещью, связанной с обещаниями, но я думаю, что это, возможно, потому, что это противоречит философии обещаний.

Учитывая следующий код:

// Assuming Auth is just a simple lib doing http requests with promises
Auth.signup()
 .then(succCall, errCall)
 .then(loginSucc, loginErr)

// My callbacks here
function succCall (){
 // OK, send second promise
 console.log('succCall');
 return Auth.login();
}

function errCall(){
 // I do some things here and now
 // I want to break out from here
 console.log('errCall');
}

function loginSucc(){
 // This is the callback of the login method when it went OK
 // I want to enter here ONLY if with go through the succCall
 console.log('loginSucc');
}

function loginErr(){
 // This is the callback of the login method when it went not ok
 // I want to enter here ONLY if with go through the succCall
 console.log('loginErr');
}

Здесь, если что-то пойдет не так в Auth.signup(), вот что будет показано:

  • errCall, логинSucc

если я делаю $q.reject() в errCall, вот что происходит:

  • errCall, логинErr

и это то, что я хочу:

  • errCall... заканчивай, стой здесь

Теперь проблема в том, что он переходит в errCall, когда регистрация идет неправильно, это хорошо, но затем он входит в loginSucc...

Я хочу вырваться из цепочки then, когда встречается какой-либо errorCallback (здесь это errCall или loginErr).

-- РЕДАКТИРОВАТЬ --

Я думаю, что меня неправильно поняли, я хочу полностью разорвать цепочку, не проверяя никаких других «тогда», если что-то пошло не так.

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

// Just like if i did the following but by chainning "then" methods
// My callbacks here
function succCall (){
 // OK, send second promise
 return Auth.login().then(loginSucc, loginErr);
}

Я хочу сказать, что мне не нужен только один обработчик ошибок, если у меня много цепочек "тогда"


person darkylmnx    schedule 21.12.2015    source источник
comment
Я думаю, чтобы разорвать цепочку, вам нужно reject промис внутри функции errCall.   -  person koox00    schedule 21.12.2015
comment
@ koox00 нет, отклонение просто приводит к следующей ошибке, поэтому здесь loginErr, и это то, чего я не хочу   -  person darkylmnx    schedule 21.12.2015
comment
У @SoluableNonagon есть правильный ответ — используйте catch и пропустите свои функции errCall и loginErr и поместите их логику в catch. Вам придется посмотреть на ошибку, которую обратный вызов catch поймал, но это единственный способ добиться того, что вы хотите.   -  person Adam    schedule 21.12.2015
comment
вопрос был не в том, чтобы найти альтернативу, потому что то, что вы здесь рассказываете, это альтернатива, это не то, что вырывается из цепи. Я знаю этот метод, но это не тот, который мне нужен, но все равно спасибо   -  person darkylmnx    schedule 21.12.2015
comment
можно попробовать разбить на 2 части? Auth.signup() .then(succCall, errCall) , succCall.then(loginSucc, loginErr), что-то в этом роде   -  person koox00    schedule 21.12.2015
comment
в идеале это то, что я хотел бы, но как это сломать? @koox00   -  person darkylmnx    schedule 21.12.2015
comment
Единственный вариант — вернуть обещание, которое никогда не будет разрешено, вместо отклонения в первом обработчике ошибок. Смотрите мой обновленный ответ ниже.   -  person lex82    schedule 21.12.2015
comment
То, чего вы пытаетесь достичь, считается анти-шаблоном. В идеале цепочка должна быть единым успешным сценарием с уловом для обработки потенциальных ошибок. Если вы хотите сделать больше ветвлений в цепочке, это легко станет кошмаром для обслуживания. Спросите себя, почему вы действительно хотите обрабатывать промежуточные ошибки. Вы хотите что-то записать? Просто используйте конкретное сообщение об ошибке. Вы хотите очистить? Используйте определенный тип ошибки. Вы хотите сделать что-то продвинутое? Разорвите цепь по частям.   -  person Vincent van der Weele    schedule 21.12.2015


Ответы (4)


Фактически происходит следующее:

    try {
        try {
            var a = succCall();
        } catch(e1) {
            a = errCall(e1);
        }
        var b = loginSucc(a);
    } catch(e2) {
        b = loginErr(e2);
    }

Вы можете вырваться из цепочки, позвонив

return $q.reject('Reason Err was called');

в вашей функции errCall().

EDIT: Как заметил OP, вызвав $q.reject, код войдет в функцию loginErr. В качестве альтернативы вы можете изменить свой код следующим образом:

Auth.signup()
.then(function(a) {
    succCall()
    return loginSucc(a).then(null, loginErr);
}, errCall)

Вы можете прочитать больше в этих двух вопросах SO:

  1. Разорвать цепочку обещаний
  2. Break Out of then promises в Angularjs

Это также полезно прочитать: Сведение цепочек обещаний

person gyosifov    schedule 21.12.2015
comment
ваш ответ не совсем точен в том смысле, что он не разрывает цепочку, а переходит ко второй ошибке, чего я не хочу. Я хочу вырваться из цепочки then, когда встречается какой-либо errorCallback (здесь это errCall или loginErr). - person darkylmnx; 21.12.2015
comment
да, это то, чего я не хочу, запускать шаблон обещания в стиле пирамиды, но я думаю, что нет метода обещания, который можно было бы сломать, как в циклах или в синтаксисе переключателя. - person darkylmnx; 21.12.2015
comment
Я не нашел другого способа, нет. Вы всегда можете использовать goto - я шучу! - person gyosifov; 21.12.2015
comment
@darkylmnx что я обычно делаю, чтобы решить эту проблему, так это использовать перехват в конце и избегать обработки ошибок в середине. Если кто-то будет отвергнут на полпути, это пойдет на улов. Фильтр ошибок, который вы передаете .then, существует, чтобы вы могли превратить его в успешную или лучшую ошибку для следующего элемента в цепочке. - person Kevin B; 21.12.2015
comment
@KevinB в случае, если я регистрируюсь, то, например, автоматический вход в систему, как здесь, если у меня нет двух разных обработчиков ошибок, я не могу отобразить на своей странице соответствующую реакцию, которую я хочу для каждого из них. Теперь, если мне нужно сделать что-то if->else в единственном обработчике ошибок, который я должен определить, какое действие нужно выполнить, мне нет смысла использовать промисы, я мог бы использовать обратные вызовы или метод gyosifov. - person darkylmnx; 21.12.2015
comment
Если оба случая приводят к отображению сообщения пользователю, было бы еще более целесообразно использовать .catch в сочетании с фильтром ошибок .then. Попросите .then преобразовать ошибку в сообщение, которое вы хотите отобразить, а затем отобразить его в .catch, что делает код более сухим. renderError(message) - person Kevin B; 21.12.2015
comment
@KevinB это означает добавление одного улова, логики многих разных вещей, которая действительно неаккуратна, иметь уловки, которая делает, если (вы ошибка логини) иначе если (вы ошибка регистрации) иначе если (вы ...) это довольно некрасиво видеть, это то, чего я хочу избежать - person darkylmnx; 21.12.2015
comment
Зависит от того, все ли они делают x или каждый из них приводит к чему-то совершенно другому. Обычно представление сообщения об ошибке довольно стандартно и не требует логики if else. - person Kevin B; 21.12.2015

Функция errCall должна возвращать обещание, и это обещание нужно отклонить, чтобы сработала функция loginErr.

function errCall(){
   // i do some things here and now

   return $q(function(resolve, reject) {
        // auto reject
        reject();
   });


}

В качестве альтернативы попробуйте .catch:

Auth.signup()
 .then(succCall)
 .then(loginSucc)
 .catch(function(err){
      // caught error, problem is you won't know which function errored out, so you'll need to look at the error response
 });
person SoluableNonagon    schedule 21.12.2015
comment
это именно то, чего id не хочет, я не хочу, чтобы что-то не было сделано, если оно входит в errCall, все остальное следует игнорировать, как и в базовом синтаксисе обратного вызова. Во-вторых, для меня обратный вызов succCall, а не errCall, это главное здесь - person darkylmnx; 21.12.2015

Просто не передавайте errCall или loginErr в then и используйте catch() в конце цепочки обещаний, и она будет прервана при первой ошибке, которая будет передана catch(). Если вы хотите явно обработать ошибку Auth.signup(), тогда ваш errCall должен выглядеть так:

function (err) {
  if(isFatal(err)) {
    return Promise.reject(new Error('Fatal!')); //`catch()` handler will be called with `new Error('Fatal!')`
  } else {
    return 'something'; //next `then()` handler will be called with 'something'
  }
}
person Zuker    schedule 21.12.2015
comment
это означает, что я должен проверять каждый успех или разрешать значение обратного вызова, чтобы проверить, не сработал ли предыдущий, что довольно уродливо, я нахожу - person darkylmnx; 21.12.2015
comment
Нет, вам не нужно проверять каждое значение обратного вызова, если только вы не хотите явно обрабатывать конкретный результат сбоя обещания. - person Zuker; 22.12.2015
comment
ваш метод не может позволить мне обрабатывать каждую ошибку отдельно, что я и искал, но я нашел душу, спасибо. - person darkylmnx; 22.12.2015

Ваш лучший вариант — вернуть обещание, которое никогда не разрешается из errCall():

function errCall() {
     console.log('errCall');
     return $q.defer().promise;
}

Но вы правы, то, что вы пытаетесь сделать, «противоречит философии обещаний».

Почему вы не должны этого делать

Конечно, проблема заключается в том, что ваша функция loginSucc вызывается при возникновении ошибки во время оценки первого промиса. Однако это можно исправить, отклонив еще раз в errCall, как уже указывали другие. Но не стоит пытаться избежать выполнения loginErr, иначе с вашим кодом что-то концептуально не так.

Оценка выражения

Auth.signup().then(succCall, errCall)

дает другое обещание. Контракт заключается в том, что промис имеет метод then(), принимающий два обратных вызова в качестве параметров, один для успеха и один для неудачи. Контракт также заключается в том, что обратный вызов успеха вызывается, когда обещание оценивается успешно, и что обратный вызов ошибки/сбоя вызывается во всех других случаях. Если вы намерены никогда не вызывать ни один из них, не используйте обещание!

person lex82    schedule 21.12.2015
comment
что-то не так в том, что вы сказали, я хочу, чтобы один из них запускался Auth.signup().then(succCall, errCall), но я не хочу, чтобы следующий затем Auth.signup().then(succCall, errCall). затем (loginSucc, loginErr) для запуска, потому что предыдущий был отклонен, это то, чего я пытаюсь добиться, но да, это может противоречить философии обещаний - person darkylmnx; 21.12.2015
comment
Я понимаю вашу проблему. Я обновил свой ответ, поэтому надеюсь, что теперь понятно, что я имею в виду. - person lex82; 21.12.2015
comment
спасибо, я попробую это, я обнаружил, что выполнение $q(function(){return null;}) также отменяет распространение обещания blog.zeit.io/ - person darkylmnx; 22.12.2015