Umiejscowienie połowu PRZED i PO następnie

103

Mam problem ze zrozumieniem różnicy między wstawieniem .catchPRZED i PO następnie w zagnieżdżonej obietnicy.

Alternatywa 1:

test1Async(10).then((res) => {
  return test2Async(22)
    .then((res) => {
      return test3Async(100);
    }).catch((err) => {
      throw "ERROR AFTER THEN";
    });
}).then((res) => {
  console.log(res);
}).catch((err) => {
  console.log(err);
});

Alternatywa 2:

test1Async(10).then((res) => {
   return test2Async(22)
     .catch((err) => {
        throw "ERROR BEFORE THEN";
      })
      .then((res) => {
        return test3Async(100);
      });
  }).then((res) => {
    console.log(res);
  }).catch((err) => {
    console.log(err);
  });

Zachowanie każdej funkcji jest następujące, test1 <0kończy się niepowodzeniem, jeśli number to test2, nie powiedzie się, jeśli number to, > 10a test3 nie powiedzie się, jeśli number nie 100. W tym przypadku test2 kończy się niepowodzeniem.

Próbowałem uruchomić i spowodować niepowodzenie testu test2Async, zarówno PRZED, jak i PO, a następnie zachowuje się w ten sam sposób, co nie oznacza wykonania testu test3Async. Czy ktoś może mi wyjaśnić główną różnicę w umieszczaniu połowu w różnych miejscach?

W każdej funkcji I console.log('Running test X'), aby sprawdzić, czy zostanie wykonana.

To pytanie pojawia się z powodu poprzedniego wątku, który opublikowałem Jak zamienić zagnieżdżone wywołanie zwrotne w obietnicę? . Myślę, że to inny problem i warto zgłosić inny temat.

Zanko
źródło
zarówno. then, jak i .catch mogą zmienić obietnicę ... więc nie jestem pewien, skąd pochodzi nieporozumienie. Jeśli umieścisz catch przed .then, złapie odrzucenia, które wydarzyły się przed .then i. Then.
Kevin B
Przepraszam, jeśli moje pytanie nie było jasne. Ale w tym przypadku, jak powiedziałem, oba przypadki zachowują się tak samo, więc nie widzę różnicy. Czy możesz mi powiedzieć, kiedy założyliśmy haczyk PRZED, a kiedy zdecydowaliśmy się to zrobić PO? umieszczenie tego po wydaje się naprawdę intuicyjne i powszechne. Tylko nie jestem pewien, dlaczego czasami umieszczamy to wcześniej
Zanko
Jeśli wykonują to samo, dzieje się tak po prostu dlatego, że to, co każdy z nich robi, nie zmienia wyniku w tym konkretnym przypadku. Niewielka zmiana w jednym z nich może zmienić wynik.
Kevin B
Co masz na myśli przez „zmianę wyniku”. Przepraszam, jestem naprawdę zdezorientowany haha
Zanko
Na przykład, jeśli zamiast zgłosić błąd, po prostu nic nie zrobiłeś, obietnica zmieniłaby się z odrzucenia na rozwiązanie. To oczywiście zmieniłoby wynik, ponieważ obietnica jest teraz obietnicą rozstrzygniętą, a nie odrzuconą. (chyba że oczywiście zostało to już rozwiązane, w takim przypadku haczyk i tak by nie odbył się)
Kevin B

Odpowiedzi:

237

Więc w zasadzie pytasz, jaka jest różnica między tymi dwoma (gdzie pjest obietnica utworzona z jakiegoś poprzedniego kodu):

return p.then(...).catch(...);

i

return p.catch(...).then(...);

Istnieją różnice, gdy p rozwiązuje lub odrzuca, ale to, czy te różnice mają znaczenie, czy nie, zależy od tego, co robi kod wewnątrz .then()lub .catch()procedury obsługi.

Co się stanie, gdy zostanie prozwiązany:

W pierwszym schemacie, po prozwiązaniu, .then()wywoływana jest procedura obsługi. Jeśli ten .then()program obsługi zwróci wartość lub inną obietnicę, która ostatecznie zostanie rozwiązana, wówczas .catch()procedura obsługi jest pomijana. Ale jeśli przewodnik .then()albo wyrzuci lub zwróci obietnicę, która ostatecznie ją odrzuci, wówczas program .catch()obsługi wykona zarówno odrzucenie w pierwotnej obietnicy p, jak i błąd, który wystąpi w programie .then()obsługi.

W drugim schemacie, po prozwiązaniu, .then()wywoływana jest procedura obsługi. Jeśli ten przewodnik .then()rzuca lub zwraca obietnicę, która ostatecznie odrzuca, .catch()nie może jej złapać, ponieważ znajduje się przed nią w łańcuchu.

Więc to jest różnica # 1. Jeśli program .catch()obsługi jest AFTER, może również wychwycić błędy wewnątrz .then()obsługi.

Co się dzieje, gdy podrzuca:

Teraz, w pierwszym schemacie, jeśli obietnica zostanie podrzucona, to program .then()obsługi jest pomijany, a program .catch()obsługi zostanie wywołany tak, jak można się tego spodziewać. To, co robisz w .catch()procedurze obsługi, określa, co zostanie zwrócone jako wynik końcowy. Jeśli po prostu zwrócisz wartość z .catch()procedury obsługi lub zwrócisz obietnicę, która w końcu zostanie rozwiązana, wówczas łańcuch obietnic przełącza się do stanu rozwiązanego, ponieważ „obsłużyłeś” błąd i powróciłeś normalnie. Jeśli wyrzucisz lub zwrócisz odrzuconą obietnicę do przewodnika .catch(), wówczas zwrócona obietnica pozostaje odrzucona.

W drugim schemacie, jeśli obietnica zostanie podrzucona, .catch()wywoływany jest przewodnik. Jeśli zwrócisz normalną wartość lub obietnicę, która ostatecznie zostanie rozwiązana przez .catch()procedurę obsługi (w ten sposób „obsłuży” błąd), wówczas łańcuch obietnicy przełącza się do stanu rozwiązanego, a .then()procedura obsługi po zakończeniu .catch()zostanie wywołana.

Więc to jest różnica # 2. Jeśli .catch()procedura obsługi jest PRZED, to może obsłużyć błąd i pozwolić, aby .then()procedura obsługi nadal była wywoływana.

Kiedy używać których:

Użyj pierwszego schematu, jeśli chcesz, aby tylko jeden .catch()program obsługi mógł wychwycić błędy w oryginalnej obietnicy plub w .then()module obsługi, a odrzucenie z programu ppowinno pominąć .then()procedurę obsługi.

Użyj drugiego schematu, jeśli chcesz być w stanie wychwycić błędy w pierwotnej obietnicy pi być może (w zależności od warunków), pozwolić łańcuchowi obietnic na kontynuację po rozwiązaniu, wykonując w ten sposób .then()procedurę obsługi.

Druga opcja

Jest jeszcze jedna opcja korzystania z obu wywołań zwrotnych, do których możesz przejść .then()jak w:

 p.then(fn1, fn2)

Gwarantuje to, że tylko jeden z fn1lub fn2kiedykolwiek zostanie wywołany. Jeśli pustąpi, fn1zostanie wezwany. Jeśli podrzuci, fn2zostanie wezwany. Żadna zmiana wyniku fn1nie może sprawić, fn2że ktoś Cię wezwie lub odwrotnie. Tak więc, jeśli chcesz mieć absolutną pewność, że tylko jeden z twoich dwóch programów obsługi jest wywoływany, niezależnie od tego, co dzieje się w samych modułach obsługi, możesz użyć p.then(fn1, fn2).

jfriend00
źródło
17
Pytanie dotyczy konkretnie kolejności .then()i .catch(), na które odpowiadasz. Dodatkowo dajesz kilka wskazówek, kiedy użyć jakiej kolejności, gdzie myślę, że należy wspomnieć o trzeciej opcji, a mianowicie o przekazaniu zarówno procedury obsługi sukcesu, jak i błędu do .then () . W takim przypadku zostanie wywołany co najwyżej jeden program obsługi.
ArneHugo
7
@ArneHugo - Dobra sugestia. Dodałem.
jfriend00
Czy więc podczas Promise Chaining możemy napisać. Then .catch .catch., A potem rodzaj scenariuszy?
Kapil Raghuwanshi
@KapilRaghuwanshi, tak, możesz go użyć do przekazania wartości domyślnej w przypadku niepowodzenia. ie Promise.reject(new Error("F")).then(x => x).catch(e => {console.log(e); return [1]}).then(console.log)i Promise.resolve([2]).then(x => x).catch(e => [1]).then(console.log)
CervEd
1
@DmitryShvedov - Jak się domyśliłem, to źle .then(this.setState({isModalOpen: false})). Nie przekazujesz odwołania do funkcji, aby .then()kod w parenach był wykonywany natychmiast (zanim obietnica zostanie rozwiązana). Tak powinno być .then(() => this.setState({isModalOpen: false})).
jfriend00
31

Odpowiedź jfriend00 jest doskonała, ale pomyślałem, że dobrym pomysłem byłoby dodanie analogicznego kodu synchronicznego.

return p.then(...).catch(...);

jest podobny do synchronicznego:

try {
  iMightThrow() // like `p`
  then()
} catch (err) {
  handleCatch()
}

Jeśli iMightThrow()nie wyrzuci, then()zostanie wezwany. Jeśli rzuca (lub jeśli then()sam rzuca), handleCatch()zostanie wywołany. Zwróć uwagę, że catchblok nie ma kontroli nad tym, czy thenjest wywoływany, czy nie .

Z drugiej strony,

return p.catch(...).then(...);

jest podobny do synchronicznego:

try {
  iMightThrow()
} catch (err) {
  handleCatch()
}

then()

W tym przypadku, jeśli iMightThrow()nie rzuci, to then()wykona. Jeśli tak, to należałoby handleCatch()zdecydować, czy then()zostanie wywołany, ponieważ jeśli handleCatch()powtórzy, to then()nie zostanie wywołany, ponieważ wyjątek zostanie natychmiast wyrzucony do wywołującego. Jeśli z handleCatch()wdziękiem poradzi sobie z problemem, then()zostanie wywołany.

akivajgordon
źródło
to dobre wytłumaczenie, ale możesz zapakować sierotę then()w afinally{...}
tyskr
2
@ 82 Tuskers, czy na pewno? Jeśli mogę umieścić then()w finally{...}, nie byłoby nieprawidłowo nazwać nawet jeśli handleCatch()rzuca? Pamiętaj, że moim celem było pokazanie analogicznego kodu synchronicznego, a nie zasugerowanie różnych sposobów obsługi wyjątków
akivajgordon
Tak więc, jeśli chcemy obsłużyć wszystkie przypadki, ale nadal łańcuch .then (), najlepiej byłoby użyć .then (zrób coś) .catch (zarejestruj błąd i zaktualizuj stan) .then (zrób coś innego) .catch (log err). gdzie próbujemy złapać w każdym punkcie, ale także kontynuować wykonywanie poleceń?
anna