Kiedy .ten (sukces, porażka) jest uważany za antypattern dla obietnic?

188

Rzuciłem okiem na często zadawane pytania dotyczące obietnicy bluebird , w których wspomina, że .then(success, fail)jest to antypattern . Nie do końca rozumiem jego wyjaśnienie dotyczące próby złapania. Co jest z tym nie tak?

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

Wygląda na to, że przykład sugeruje następujące postępowanie jako prawidłowy sposób.

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

Co za różnica?

użytkownik2127480
źródło
1
then().catch()jest bardziej czytelny, ponieważ nie musisz szukać przecinka i sprawdzać, czy jest to wywołanie zwrotne dla gałęzi sukcesu lub niepowodzenia.
Krzysztof Safjanowski,
7
@KevinB: Jest duża różnica, sprawdź odpowiedzi
Bergi,
12
@KrzysztofSafjanowski - zdewastowany argumentem „wygląda lepiej”. Totalnie źle!
Andrey Popov
6
UWAGA: Kiedy używasz .catch, nie wiesz, który krok spowodował problem - w ostatnim thenlub gdzieś w górę łańcucha obietnic. Ma to więc swoją własną wadę.
vitaly-t
2
Zawsze dodam nazwy funkcji do parametrów obietnicy .then (), aby była czytelna, tj.some_promise_call() .then(function fulfilled(res) { logger.log(res) }, function rejected(err) { logger.log(err) })
Shane Rowatt

Odpowiedzi:

215

Co za różnica?

.then()Rozmowa powróci obietnicę, że zostanie odrzucona w przypadku oddzwaniania zgłasza błąd. Oznacza to, że gdy Twój sukces się loggernie powiedzie, błąd zostanie przekazany do następującego .catch()wywołania zwrotnego, ale nie do failwywołania zwrotnego, które towarzyszy success.

Oto schemat kontrolny :

kontroluj schemat wtedy z dwoma argumentami kontrolny schemat blokowy łańcucha następnie

Aby wyrazić to w kodzie synchronicznym:

// 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);
}

Drugi log(który jest jak pierwszy argument .then()) zostanie wykonany tylko w przypadku, gdy nie wystąpi żaden wyjątek. Znakowany blok i breakoświadczenie czujesz się trochę dziwne, to jest rzeczywiście to, co python ma try-except-elsedla (zalecane lektury!).

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

Program catchrejestrujący będzie także obsługiwał wyjątki od wywołania programu rejestrującego sukces.

Tyle o różnicy.

Nie do końca rozumiem jego wyjaśnienie dotyczące próby złapania

Argument jest taki, że zwykle chcesz wychwytywać błędy na każdym etapie przetwarzania i że nie powinieneś używać go w łańcuchach. Oczekuje się, że masz tylko jeden końcowy moduł obsługi, który obsługuje wszystkie błędy - podczas gdy podczas korzystania z „antipattern” błędy w niektórych wywołaniach zwrotnych nie są obsługiwane.

Jednak ten wzorzec jest w rzeczywistości bardzo przydatny: gdy chcesz obsłużyć błędy, które wystąpiły dokładnie w tym kroku, i chcesz zrobić coś zupełnie innego, gdy nie wystąpił błąd - tj. Gdy błąd jest nie do naprawienia. Pamiętaj, że to rozgałęzia twój przepływ kontroli. Oczywiście jest to czasem pożądane.


Co jest z tym nie tak?

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

Że musisz powtórzyć oddzwonienie. Raczej chcesz

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

Możesz również rozważyć użycie .finally()do tego celu.

Bergi
źródło
7
to jest najbardziej pomocne wyjaśnienie , które przeczytałem w ciągu kilku dni (i dużo przeczytałem). Nie potrafię wyjaśnić, jak wdzięczny jestem! :) Myślę, że powinieneś bardziej podkreślić różnicę między tymi dwoma, które .catchbędą wychwytywać błędy nawet w funkcji sukcesu . Osobiście uważam to za bardzo błędne, ponieważ kończysz z jednym punktem wejścia błędu, który może uzyskać wiele błędów z wiele działań, ale to mój problem. W każdym razie - dzięki za informację! Czy nie masz narzędzia komunikacji online, które chcesz udostępnić, abym mógł prosić o kilka rzeczy więcej? : P
Andrey Popov
2
Mam nadzieję, że daje to więcej pozytywnych opinii tutaj. Zdecydowanie jedno z najlepszych wyjaśnień ważnego Promisemechanika na tej stronie.
Patrick Roberts,
2
.done()nie jest częścią standardu, prawda? Przynajmniej MDN nie podaje tej metody. To było by pomocne.
ygoe
1
@ygoe Rzeczywiście. donejest rzeczą Bluebird, która została zasadniczo przestarzała przez then+ wykrywanie nieobsługiwanego odrzucenia.
Bergi,
1
tylko uwaga od ślepego na kolory: diagramy nie mają sensu :)
Benny K
37

Te dwa nie są do końca identyczne. Różnica polega na tym, że pierwszy przykład nie złapie wyjątku zgłoszonego w successmodule obsługi. Tak więc, jeśli twoja metoda powinna zwracać tylko rozwiązane obietnice, jak to często bywa, potrzebujesz końcowej catchprocedury obsługi (lub jeszcze innej thenz pustym successparametrem). Jasne, może się zdarzyć, że twój thenprogram obsługi nie zrobi niczego, co mogłoby potencjalnie zawieść, w takim przypadku użycie jednego 2-parametru thenmoże być w porządku.

Uważam jednak, że sens tekstu, do którego się odnosisz, thenjest najbardziej przydatny w porównaniu do wywołań zwrotnych w jego zdolności do łączenia szeregu asynchronicznych kroków, a kiedy faktycznie to robisz, 2-parametrowa forma thensubtelnie nie zachowuje się tak, jak się spodziewano , z powyższego powodu. Jest to szczególnie sprzeczne z intuicją, gdy jest używane w połowie łańcucha.

Jako ktoś, kto wykonał wiele skomplikowanych zadań asynchronicznych i wpadł na takie zakręty bardziej, niż się to przyznaję, naprawdę zalecam unikanie tego anty-wzorca i stosowanie osobnego podejścia obsługi.

acjay
źródło
18

Patrząc na zalety i wady obu tych elementów, możemy wyliczyć, które z nich jest odpowiednie w danej sytuacji. Są to dwa główne podejścia do realizacji obietnic. Oba mają swoje plusy i minusy

Podejście połowowe

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

Zalety

  1. Wszystkie błędy są obsługiwane przez jeden blok catch.
  2. Nawet wyłapuje dowolny wyjątek w tym bloku.
  3. Łączenie wielu zwrotnych sukcesów

Niedogodności

  1. W przypadku tworzenia łańcuchów trudno jest wyświetlać różne komunikaty o błędach.

Podejście do sukcesu / błędu

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

Zalety

  1. Otrzymujesz precyzyjną kontrolę błędów.
  2. Możesz mieć wspólną funkcję obsługi błędów dla różnych kategorii błędów, takich jak błąd db, błąd 500 itp.

Wady

  1. Nadal będziesz potrzebować innego, catchjeśli chcesz obsługiwać błędy generowane przez wywołanie zwrotne sukcesu
aWebDeveloper
źródło
Dla kogoś, kto musi debugować problemy z produkcją za pomocą tylko pliku dziennika, wolę Podejście Sukcesu / Błędu, ponieważ daje ono możliwość utworzenia przyczynowego łańcucha błędów, który można rejestrować na granicach wyjściowych aplikacji.
Shane Rowatt
pytanie. powiedzmy, że wykonuję wywołanie asynchroniczne, które wykonuje jedną z kilku rzeczy: 1) zwraca pomyślnie (kod statusu 2xx), 2) zwraca bez powodzenia (kod 4xx lub 5xx), ale nie jest odrzucane per se, 3) lub w ogóle nie zwraca ( połączenie internetowe jest wyłączone). W przypadku nr 1 hit callback w. Then zostaje trafiony. W przypadku nr 2 trafiony jest błąd wywołania zwrotnego w. Następnie. W przypadku nr 3 wywoływany jest .catch. To poprawna analiza, prawda? Przypadek nr 2 jest najtrudniejszy technicznie, bo technicznie 4xx lub 5xx nie jest odrzuceniem, wciąż powraca. Dlatego musimy sobie z tym poradzić wewnątrz. .... Czy moje rozumowanie jest prawidłowe?
Benjamin Hoffman
„W przypadku nr 2 trafiony jest błąd wywołania zwrotnego w. Następnie. W przypadku nr 3 wywoływany jest .catch. To jest poprawna analiza, prawda?” - Tak działa pobieranie
aWebDeveloper
2

Proste wyjaśnienie:

W ES2018

Po wywołaniu metody catch z argumentem onRejected podejmowane są następujące kroki:

  1. Obiecajmy, że ta wartość będzie.
  2. Powrót ? Wywołaj (obiecaj, „następnie”, «niezdefiniowany, onRejected»).

to znaczy:

promise.then(f1).catch(f2)

równa się

promise.then(f1).then(undefiend, f2)
bitfishxyz
źródło
1

Użycie .then().catch()pozwala włączyć Łańcuch obietnicy, który jest wymagany do realizacji przepływu pracy. Może być konieczne odczytanie niektórych informacji z bazy danych, a następnie przekazanie ich do asynchronicznego interfejsu API, a następnie manipulowanie odpowiedzią. Możesz zepchnąć odpowiedź z powrotem do bazy danych. Obsługa wszystkich tych przepływów pracy za pomocą koncepcji jest wykonalna, ale bardzo trudna do zarządzania. Lepszym rozwiązaniem będzie then().then().then().then().catch()otrzymywanie wszystkich błędów za jednym razem catch i pozwala zachować łatwość konserwacji kodu.

Jayant Varshney
źródło
0

Korzystanie then()i catch()pomoc w łańcuchu obsługi sukcesu i niepowodzenia w obietnicy. catch()działa na podstawie obietnicy zwróconej przez then(). Radzi sobie,

  1. Jeśli obietnica została odrzucona. Zobacz nr 3 na zdjęciu
  2. Jeśli wystąpił błąd w module obsługi sukcesu funkcji then (), między numerami linii od 4 do 7 poniżej. Zobacz # 2.a na zdjęciu (funkcja oddzwaniania w przypadku niepowodzenia then()tego nie obsługuje).
  3. Jeśli wystąpił błąd w module obsługi błędów funkcji then (), wiersz 8 poniżej. Zobacz # 3.b na zdjęciu.

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. });

wprowadź opis zdjęcia tutaj

Uwaga : Wiele razy program obsługi błędów może nie zostać zdefiniowany, jeśli catch()jest już napisany. EDYCJA: reject()powoduje wywołanie catch()tylko wtedy, gdy procedura obsługi błędów w niethen() jest zdefiniowana. Zauważ # 3 na zdjęciu do . Jest wywoływany, gdy moduł obsługi w linii 8 i 9 nie jest zdefiniowany.catch()

Ma to sens, ponieważ zwrócona przez then()nie obietnica nie zawiera błędu, jeśli zajmuje się nią wywołanie zwrotne.

VenCKi
źródło
Strzałka od cyfry 3 do catchwywołania zwrotnego wydaje się nieprawidłowa.
Bergi
Dzięki! Z wywołaniem zwrotnym błędu zdefiniowanym w then (), nie jest on wywoływany (wiersz nr 8 i nr 9 we fragmencie kodu). # 3 przywołuje jedną z dwóch strzałek. Ma to sens, ponieważ obietnica zwrócona do tego czasu () nie zawiera błędu, jeśli zajmuje się nią wywołanie zwrotne. Edytowałem odpowiedź!
VenCKi
-1

Zamiast słów dobry przykład. Poniższy kod (jeśli pierwsza obietnica została rozwiązana):

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

jest identyczny z:

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

Ale przy odrzuconej pierwszej obietnicy nie jest to identyczne:

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'); }
)
ktretyak
źródło
4
To nie ma sensu, czy możesz usunąć tę odpowiedź? Wprowadza w błąd i odwraca uwagę od prawidłowej odpowiedzi.
Andy Ray
@AndyRay, to nie ma sensu w prawdziwej aplikacji, ale warto zrozumieć działanie obietnic.
ktretyak