Коли .then (успіх, невдача) вважається антипатрітетом для обіцянок?


188

Я переглянув питання щодо поширених запитань щодо синьої пташки , в яких згадується, що .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або десь ще в ланцюзі обіцянок. Тож у нього є свій недолік.
vitaly-t

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

Відповіді:


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також буде обробляти винятки з виклику реєстратора успіху.

Стільки для різниці.

Я не зовсім розумію його пояснення щодо спроби та лову

Аргумент полягає в тому, що зазвичай ви хочете вловлювати помилки на кожному етапі обробки, і що ви не повинні використовувати їх у ланцюгах. Очікується, що у вас є лише один кінцевий обробник, який обробляє всі помилки - в той час, коли ви використовуєте "антипатерн", помилки в деяких тодішніх зворотних викликах не обробляються.

Однак ця модель насправді дуже корисна: коли ви хочете обробити помилки, що трапилися саме на цьому кроці, і ви хочете зробити щось зовсім інше, коли не сталася помилка - тобто коли помилка не може бути усунена. Майте на увазі, що це розгалужує ваш контрольний потік. Звичайно, це іноді бажано.


Що поганого в цьому нижче?

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- це річ Блакитної птиці, яка в основному застаріла за допомогою thenвиявлення + необробленого відхилення.
Берги

1
просто зауваження з барвінку: діаграми не мають сенсу :)
Benny K

37

Два не зовсім однакові. Різниця полягає в тому, що перший приклад не вловить виняток, який кидається у ваш successобробник. Отже, якщо ваш метод повинен повертати лише вирішені обіцянки, як це часто трапляється, вам потрібен кінцевий catchобробник (або ще один thenіз порожнім successпараметром). Звичайно, може бути, що ваш thenобробник не зробить нічого, що може потенційно вийти з ладу, і в цьому випадку використання одного 2-параметра thenможе бути нормальним.

Але я вважаю, що текст тексту, з яким ви пов’язані, полягає в тому, що thenв основному корисний проти зворотних зворотних дзвінків у його здатності ланцюжком купу асинхронних кроків, і коли ви це робите, 2-параметрична форма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 помилка тощо.

Недоліки

  1. Ще вам знадобиться інша, catchякщо ви хочете обробляти помилки, викликані зворотним викликом успіху

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

питання. скажемо, я здійснюю виклик асинхронізації, який виконує одну з кількох речей: 1) успішно повертається (2xx код коду), 2) повертається невдало (4xx або 5xx код), але не відхиляється як такий, 3) або взагалі не повертається ( підключення до Інтернету відключено). У випадку №1 відмічено зворотний виклик успіху в .then. У випадку №2 буде звернено повідомлення про помилку в .then. У випадку №3 викликається .catch. Це правильний аналіз, правда? Справа №2 є найбільш хитрою технікою в технічному режимі 4xx або 5xx не є відхиленням, вона все ще успішно повертається. Таким чином, нам потрібно обробити це в межах. .... Чи правильно я розумію?
Бенджамін Гофман

"У випадку №2 потрапляє зворотний виклик помилки в .then. Для випадку №3 викликається .catch. Це правильний аналіз, правда?" - Ось як працює
збірка

2

Просте пояснення:

У ES2018 році

Коли метод вилучення викликається аргументом 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. Якщо сталася помилка в обробці успіху тоді (), між номерами рядків 4 до 7 нижче. Див. № 2.а на малюнку (Помилка зворотного дзвінка увімкненаthen() не справляється з цим.)
  3. Якщо виникла помилка в обробці відмов тоді (), рядок № 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зворотного дзвінка здається неправильною.
Берги

Дякую! Якщо зворотний виклик помилки, визначений у тоді (), не викликається (рядок №8 та №9 у фрагменті коду). №3 викликає одну з двох стрілок. Це має сенс, оскільки обіцянка, повернена до цього часу (), не має помилки, якщо зворотний виклик піклується про це. Редагував відповідь!
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.