Когда .then (успех, неудача) считается антипаттером для обещаний?


188

Я посмотрел на FAQ об синей птице , в котором упоминается, что .then(success, fail)это антипаттерн . Я не совсем понимаю его объяснение, что касается попытки поймать. Что не так с этим следующим?

some_promise_call()
.then(function(res) { logger.log(res) }, function(err) { logger.log(err) })

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

some_promise_call()
.then(function(res) { logger.log(res) })
.catch(function(err) { logger.log(err) })

Какая разница?


1
then().catch()является более читабельным, так как вам не нужно искать запятую и исследовать, является ли это обратным вызовом для ветки успеха или неудачи.
Кшиштоф Сафьяновский

7
@KevinB: Есть большая разница, проверьте ответы
Берги

12
@KrzysztofSafjanowski - опустошен аргументом «выглядит лучше». Совершенно неправильно!
Андрей Попов

6
ПРИМЕЧАНИЕ. Когда вы используете .catch, вы не знаете, какой шаг вызвал проблему - внутри последнего thenили где-то еще в цепочке обещаний. Так что у него есть свой недостаток.
Виталий-т

2
Я всегда добавляю имена функций в параметры обещания .then (), чтобы сделать его читаемым, т. some_promise_call() .then(function fulfilled(res) { logger.log(res) }, function rejected(err) { logger.log(err) })
Е.

Ответы:


215

Какая разница?

.then()Вызов возвратит обещание , что будет отвергнута в случае обратного вызова выдает ошибку. Это означает, что если ваш успех loggerне удался , ошибка будет передана в следующий .catch()обратный вызов, но не в failобратный вызов, который идет вместе с success.

Вот схема потока управления :

схема управления тогда с двумя аргументами схема управления цепью захвата

Чтобы выразить это в синхронном коде:

// some_promise_call().then(logger.log, logger.log)
then: {
    try {
        var results = some_call();
    } catch(e) {
        logger.log(e);
        break then;
    } // else
        logger.log(results);
}

Второй log(который аналогичен первому аргументу .then()) будет выполняться только в том случае, если исключение не произошло. Помеченный блок иbreak оператор чувствуют себя немного странно, это именно то , try-except-elseдля чего предназначен python (рекомендуется к прочтению!).

// some_promise_call().then(logger.log).catch(logger.log)
try {
    var results = some_call();
    logger.log(results);
} catch(e) {
    logger.log(e);
}

catchРегистратор будет также обрабатывать исключения из вызова успеха регистратора.

Так много для разницы.

Я не совсем понимаю его объяснение относительно попытки поймать

Аргумент заключается в том, что обычно вы хотите отлавливать ошибки на каждом этапе обработки и не должны использовать его в цепочках. Предполагается, что у вас есть только один конечный обработчик, который обрабатывает все ошибки, в то время как при использовании «antipattern» ошибки в некоторых из обратных вызовов не обрабатываются.

Тем не менее, этот шаблон на самом деле очень полезен: когда вы хотите обработать ошибки, которые произошли именно на этом шаге, и вы хотите сделать что-то совершенно иное, когда ошибки не произошло - то есть, когда ошибка не устраняется. Помните, что это ветвит ваш поток управления. Конечно, это иногда желательно.


Что не так с этим следующим?

some_promise_call()
.then(function(res) { logger.log(res) }, function(err) { logger.log(err) })

Что вам пришлось повторить ваш обратный звонок. Ты скорее хочешь

some_promise_call()
   .catch(function(e) {
       return e; // it's OK, we'll just log it
   })
   .done(function(res) {
       logger.log(res);
   });

Вы также можете рассмотреть возможность использования .finally()для этого.


7
это самое полезное объяснение, которое я прочитал за несколько дней (и я много читал). Я не могу объяснить, как я благодарен! :) Я думаю, вам следует больше подчеркнуть разницу между ними, которая .catchбудет отлавливать ошибки даже внутри функции успеха . Лично я нахожу это крайне неправильным, поскольку вы в конечном итоге получаете одну точку входа ошибки, которая может получить несколько ошибок от несколько действий, но это моя проблема. В любом случае - спасибо за информацию! У вас нет какого-либо инструмента онлайн-общения, которым вы готовы поделиться, чтобы я мог спросить кое-что еще? : P
Андрей Попов

2
Я надеюсь, что это даст вам еще несколько голосов здесь. Определенно одно из лучших объяснений важного Promiseмеханика на этом сайте.
Патрик Робертс

2
.done()не является частью стандарта, не так ли? По крайней мере, MDN не перечисляет этот метод. Это было бы полезно.
ygoe

1
@ygoe Действительно. doneвещь Bluebird, которая в основном устарела из then-за обнаружения необработанного отказа.
Берги

1
только примечание от дальтоника: диаграммы не имеют смысла :)
Бенни К

37

Два не совсем идентичны. Разница в том, что первый пример не поймает исключение, которое выдается в вашем successобработчике. Поэтому, если ваш метод должен только когда-либо возвращать разрешенные обещания, как это часто бывает, вам нужен завершающий catchобработчик (или еще один thenс пустым successпараметром). Конечно, может случиться так, что ваш thenобработчик не сделает ничего, что потенциально может дать сбой, и в этом случае использование одного двухпараметрического параметра thenможет быть хорошо.

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

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


18

Рассматривая преимущества и недостатки обоих, мы можем сделать расчетное предположение относительно того, что соответствует ситуации. Это два основных подхода к выполнению обещаний. У обоих есть свои плюсы и минусы

Поймать подход

some_promise_call()
.then(function(res) { logger.log(res) })
.catch(function(err) { logger.log(err) })

преимущества

  1. Все ошибки обрабатываются одним блоком перехвата.
  2. Даже ловит любое исключение в тогдашнем блоке.
  3. Объединение нескольких успешных обратных вызовов

Недостатки

  1. В случае формирования цепочки становится трудно отображать разные сообщения об ошибках.

Подход Успех / Ошибка

some_promise_call()
.then(function success(res) { logger.log(res) },
      function error(err) { logger.log(err) })

преимущества

  1. Вы получаете детальный контроль ошибок.
  2. Вы можете иметь общую функцию обработки ошибок для различных категорий ошибок, таких как ошибка db, ошибка 500 и т. Д.

Disavantages

  1. Вам все еще понадобится другой, catchесли вы хотите обработать ошибки, вызванные обратным вызовом успеха

Для тех, кому нужно отлаживать производственные проблемы, используя только файл журнала, я предпочитаю подход «Успех / Ошибка», поскольку он дает возможность создать цепочку причинных ошибок, которую можно регистрировать на границах выхода вашего приложения.
Шейн Роватт

вопрос. скажем, я делаю асинхронный вызов, который выполняет одно из следующих действий: 1) возвращает успешно (код состояния 2xx), 2) возвращает неудачно (код 4xx или 5xx), но не отклоняется само по себе, 3) или не возвращается вообще ( интернет не работает). Для случая # 1, обратный вызов успеха в .then ударил. Для случая № 2 происходит обратный вызов ошибки в .then. Для случая № 3 вызывается .catch. Это правильный анализ, верно? Случай №2 сложнее всего, поскольку технически 4хх или 5хх не является отказом, но все равно успешно возвращается. Таким образом, мы должны справиться с этим в рамках .then. .... Правильно ли мое понимание?
Бенджамин Хоффман

«Для случая № 2 происходит обратный вызов ошибки в .then. Для случая № 3 вызывается .catch. Это правильный анализ, верно?» - Вот как работает fetch
aWebDeveloper

2

Простое объяснение:

В ES2018

Когда метод catch вызывается с аргументом onRejected, предпринимаются следующие шаги:

  1. Пусть обещание будет этим значением.
  2. Возвращение ? Вызвать (обещание, «затем», «не определено, отклонено»).

это значит:

promise.then(f1).catch(f2)

равно

promise.then(f1).then(undefiend, f2)

1

Использование .then().catch()позволяет вам включить Promise Chaining, которая требуется для выполнения рабочего процесса. Возможно, вам понадобится прочитать некоторую информацию из базы данных, затем вы захотите передать ее асинхронному API, а затем захотите манипулировать ответом. Вы можете отправить ответ обратно в базу данных. Обработка всех этих рабочих процессов с вашей концепцией выполнима, но очень сложна в управлении. Лучшим решением будет получение then().then().then().then().catch()всех ошибок за один раз и возможность сохранения кода.


0

Использование then()и catch()помогает связать успех и обработчик ошибок на обещании. catch()работает по обещанию then(). Это обрабатывает,

  1. Если обещание было отклонено. Смотрите № 3 на картинке
  2. Если произошла ошибка в успешном обработчике then (), между строками от 4 до 7 ниже. См. # 2.a на картинке (функция обратного вызова при сбое then()не обрабатывает это.)
  3. Если в обработчике сбоя then () произошла ошибка, номер строки 8 ниже. Смотрите # 3.b на картинке.

1. let promiseRef: Promise = this. aTimetakingTask (false); 2. promiseRef 3. .then( 4. (result) => { 5. /* successfully, resolved promise. 6. Work on data here */ 7. }, 8. (error) => console.log(error) 9. ) 10. .catch( (e) => { 11. /* successfully, resolved promise. 12. Work on data here */ 13. });

введите описание изображения здесь

Примечание . Во многих случаях обработчик ошибок может не определяться, если catch()он уже записан. EDIT: reject()результат вызова catch()только если обработчик ошибок в then()это не определено. Обратите внимание # 3 на картинке к catch(). Он вызывается, когда обработчик в строках # 8 и 9 не определен.

Это имеет смысл, потому что обещание, возвращаемое then()не имеет ошибки, если обратный вызов позаботится об этом.


Стрелка от номера 3 до catchобратного вызова кажется неправильной.
Берги

Спасибо! С обратным вызовом ошибки, определенным в then (), он не вызывается (строки № 8 и № 9 во фрагменте кода). # 3 вызывает одну из двух стрелок. Это имеет смысл, потому что обещание, возвращаемое then (), не имеет ошибки, если об этом позаботится обратный вызов. Отредактировал ответ!
VenCKi

-1

Вместо слов хороший пример. Следующий код (если первое обещание разрешено):

Promise.resolve()
.then
(
  () => { throw new Error('Error occurs'); },
  err => console.log('This error is caught:', err)
);

идентичен:

Promise.resolve()
.catch
(
  err => console.log('This error is caught:', err)
)
.then
(
  () => { throw new Error('Error occurs'); }
)

Но с отклоненным первым обещанием это не идентично:

Promise.reject()
.then
(
  () => { throw new Error('Error occurs'); },
  err => console.log('This error is caught:', err)
);

Promise.reject()
.catch
(
  err => console.log('This error is caught:', err)
)
.then
(
  () => { throw new Error('Error occurs'); }
)

4
Это не имеет смысла, не могли бы вы удалить этот ответ? Это вводит в заблуждение и отвлекает от правильного ответа.
Энди Рэй,

@AndyRay, это не имеет смысла в реальном приложении, но имеет смысл понять работу обещаний.
кретяк
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.