Czy muszę wrócić po wcześniejszym rozwiązaniu / odrzuceniu?


262

Załóżmy, że mam następujący kod.

function divide(numerator, denominator) {
 return new Promise((resolve, reject) => {

  if(denominator === 0){
   reject("Cannot divide by 0");
   return; //superfluous?
  }

  resolve(numerator / denominator);

 });
}

Jeśli moim celem jest rejectwcześniejsze wyjście, czy powinienem returnnawykć zaraz po tym?


Odpowiedzi:


370

returnCelem jest, aby zakończyć wykonywanie funkcji po odrzuceniu i zapobiec wykonanie kodu po niej.

function divide(numerator, denominator) {
  return new Promise((resolve, reject) => {

    if (denominator === 0) {
      reject("Cannot divide by 0");
      return; // The function execution ends here 
    }

    resolve(numerator / denominator);
  });
}

W takim przypadku uniemożliwia resolve(numerator / denominator);wykonanie polecenia, które nie jest ściśle potrzebne. Jednak nadal lepiej jest zakończyć wykonywanie, aby zapobiec możliwej pułapce w przyszłości. Ponadto dobrą praktyką jest zapobieganie niepotrzebnemu uruchamianiu kodu.

tło

Obietnica może być w jednym z 3 stanów:

  1. w toku - stan początkowy. Z oczekujących możemy przejść do jednego z pozostałych stanów
  2. spełnione - udane działanie
  3. odrzucone - operacja zakończona niepowodzeniem

Kiedy obietnica zostanie spełniona lub odrzucona, pozostanie w tym stanie na czas nieokreślony (rozliczony). Odrzucenie spełnionej obietnicy lub spełnienie odrzuconej obietnicy nie przyniesie skutku.

Ten przykładowy fragment pokazuje, że chociaż obietnica została spełniona po odrzuceniu, pozostała odrzucona.

function divide(numerator, denominator) {
  return new Promise((resolve, reject) => {
    if (denominator === 0) {
      reject("Cannot divide by 0");
    }

    resolve(numerator / denominator);
  });
}

divide(5,0)
  .then((result) => console.log('result: ', result))
  .catch((error) => console.log('error: ', error));

Dlaczego więc musimy wrócić?

Chociaż nie możemy zmienić ustalonego stanu przyrzeczenia, odrzucenie lub rozstrzygnięcie nie zatrzyma wykonania pozostałej części funkcji. Funkcja może zawierać kod, który spowoduje mylące wyniki. Na przykład:

function divide(numerator, denominator) {
  return new Promise((resolve, reject) => {
    if (denominator === 0) {
      reject("Cannot divide by 0");
    }
    
    console.log('operation succeeded');

    resolve(numerator / denominator);
  });
}

divide(5, 0)
  .then((result) => console.log('result: ', result))
  .catch((error) => console.log('error: ', error));

Nawet jeśli funkcja nie zawiera obecnie takiego kodu, tworzy to potencjalną pułapkę na przyszłość. Przyszły refaktor może zignorować fakt, że kod jest nadal wykonywany po odrzuceniu obietnicy i będzie trudny do debugowania.

Zatrzymywanie wykonywania po rozwiązaniu / odrzuceniu:

Jest to standardowy przepływ kontroli JS.

  • Wróć po resolve/ reject:

  • Zwróć za pomocą resolve/ reject- ponieważ zwracana wartość wywołania zwrotnego jest ignorowana, możemy zapisać linię, zwracając instrukcję odrzucenia / rozwiązania:

  • Korzystanie z bloku if / else:

Wolę użyć jednej z returnopcji, ponieważ kod jest bardziej płaski.


28
Warto zauważyć, że kod nie będzie zachowywał się inaczej, jeśli returnjest, czy nie, ponieważ po ustawieniu stanu obietnicy nie można go zmienić, więc wywołanie resolve()po wywołaniu reject()nie zrobi nic oprócz użycia kilku dodatkowych cykli procesora. Ja sam użyłbym returnsprawiedliwego z punktu widzenia czystości i wydajności kodu, ale nie jest to wymagane w tym konkretnym przykładzie.
jfriend00

1
Spróbuj użyć Promise.try(() => { })zamiast nowej Obietnicy i unikaj używania rozstrzygania / odrzucania połączeń. Zamiast tego możesz po prostu napisać, return denominator === 0 ? throw 'Cannot divide by zero' : numerator / denominator; że używam Promise.tryjako środka do rozpoczęcia Obietnicy, a także do wyeliminowania obietnic zawartych w blokach try / catch, które są problematyczne.
kingdango

2
Dobrze wiedzieć i podoba mi się ten wzór. Jednak w tej chwili Promise.try jest sugestią z etapu 0, więc można jej używać tylko z podkładką dystansową lub przy użyciu biblioteki obietnic, takiej jak bluebird lub Q.
Ori Drori

6
@ jfriend00 Oczywiście w tym prostym przykładzie kod nie będzie zachowywać się inaczej. Ale co, jeśli masz kod po tym reject, co robi coś kosztownego, na przykład podłącz do baz danych lub punktów końcowych API? Wszystko to byłoby niepotrzebne i kosztowało cię pieniądze i zasoby, szczególnie na przykład, jeśli łączysz się z czymś takim jak baza danych AWS lub punkt końcowy API Gateway. W takim przypadku zdecydowanie użyłbyś znaku powrotu, aby uniknąć wykonania niepotrzebnego kodu.
Jake Wilson,

3
@JakeWilson - Oczywiście jest to zwykły przepływ kodu w JavaScript i nie ma nic wspólnego z obietnicami. Jeśli zakończysz przetwarzanie funkcji i nie chcesz, aby kod działał w bieżącej ścieżce kodu, wstaw return.
jfriend00,

37

Powszechnym idiomem, który może, ale nie musi, być twoja filiżanka herbaty, jest łączenie się returnz nią reject, aby jednocześnie odrzucić obietnicę i wyjść z funkcji, aby pozostała część funkcji, w tym funkcja, resolvenie została wykonana. Jeśli podoba Ci się ten styl, może sprawić, że Twój kod będzie nieco bardziej zwarty.

function divide(numerator, denominator) {
  return new Promise((resolve, reject) => {
    if (denominator === 0) return reject("Cannot divide by 0");
                           ^^^^^^^^^^^^^^
    resolve(numerator / denominator);
  });
}

Działa to dobrze, ponieważ konstruktor Promise nic nie robi z żadną wartością zwracaną, aw każdym razie resolveireject powrotnej nic.

Tego samego idiomu można użyć ze stylem wywołania zwrotnego pokazanym w innej odpowiedzi:

function divide(nom, denom, cb){
  if(denom === 0) return cb(Error("Cannot divide by zero"));
                  ^^^^^^^^^
  cb(null, nom / denom);
} 

Ponownie działa to dobrze, ponieważ osoba dzwoniąca dividenie oczekuje, że cokolwiek zwróci i nie zrobi nic z wartością zwracaną.


6
Nie lubie tego. To daje pojęcie, że zwracasz coś, czym w rzeczywistości nie jesteś. Wywołujesz funkcję odrzucania, a następnie używasz return, aby zakończyć wykonywanie funkcji. Trzymaj je na osobnych liniach, a to, co robisz, tylko dezorientuje ludzi. Czytelność kodu jest najważniejsza.
K - Toksyczność w SO rośnie.

7
@KarlMorrison, w rzeczywistości zwracasz „coś”, odrzuconą obietnicę. Myślę, że to „pojęcie”, o którym mówisz, jest bardzo osobiste. Nie ma nic złego w zwróceniu rejectstatusu
Frondor

5
@Frondor Nie sądzę, że zrozumiałeś, co napisałem. Oczywiście, że rozumiemy to, nic nie dzieje się, gdy zwracamy odrzucenie w tym samym wierszu. Ale co z programistami, którzy nie są tak przyzwyczajeni do JavaScript dołączanego do projektu? Ten rodzaj programowania zmniejsza czytelność takich osób. Dzisiejszy ekosystem JavaScript jest dość bałaganem, a ludzie rozpowszechniający tego rodzaju praktyki tylko pogorszą sprawę. To zła praktyka.
K - Toksyczność w SO rośnie.

1
@KarlMorrison Osobiste opinie! = Zła praktyka. Prawdopodobnie pomógłby nowemu programistowi Javascript zrozumieć, co się dzieje z powrotem.
Toby Caulk

1
@TobyCaulk Jeśli ludzie chcą dowiedzieć się, co robi zwrot, to nie powinni bawić się obietnicami, powinni nauczyć się podstaw programowania.
K - Toksyczność w SO rośnie.

10

Technicznie nie jest to tutaj potrzebne 1 - ponieważ obietnica może zostać rozpatrzona lub odrzucona, wyłącznie i tylko raz. Pierwszy wynik Obietnicy wygrywa, a każdy kolejny wynik jest ignorowany. To jest inne od wywołań zwrotnych w stylu węzła.

Biorąc to pod uwagę, dobrą czystą praktyką jest upewnienie się, że dokładnie jedna jest wywoływana, gdy jest to praktyczne, a w tym przypadku, ponieważ nie ma dalszego asynchronizacji / odroczenia przetwarzania. Decyzja o „wczesnym powrocie” nie różni się niczym od zakończenia jakiejkolwiek funkcji po zakończeniu pracy - w przeciwieństwie do kontynuowania niepowiązanego lub niepotrzebnego przetwarzania.

Zwrot w odpowiednim czasie (lub użycie warunkowe w inny sposób, aby uniknąć wykonania „innego” przypadku) zmniejsza ryzyko przypadkowego uruchomienia kodu w nieprawidłowym stanie lub wykonania niepożądanych efektów ubocznych; i jako taki sprawia, że ​​kod jest mniej podatny na „nieoczekiwane zerwanie”.


1 Ta odpowiedź techniczna zależy również od tego, że w tym przypadku kod po „powrocie”, jeśli zostanie pominięty, nie spowoduje skutku ubocznego. JavaScript szczęśliwie podzieli przez zero i zwróci + Infinity / -Infinity lub NaN.


Niezły przypis !!
HankCa

9

Jeśli nie „wrócisz” po rozwiązaniu / odrzuceniu, złe rzeczy (takie jak przekierowanie strony) mogą się zdarzyć po tym, jak chciałeś to zatrzymać. Źródło: Wpadłem na to.


6
+1 na przykład. Miałem problem, w którym mój program wykonywał ponad 100 nieprawidłowych zapytań do bazy danych i nie mogłem zrozumieć, dlaczego. Okazuje się, że nie „wróciłem” po odrzuceniu. To mały błąd, ale nauczyłem się mojej lekcji.
AdamInTheOculus

8

Odpowiedź Ori już wyjaśnia, że ​​nie jest to konieczne return ale jest to dobra praktyka. Zauważ, że konstruktor obietnicy jest bezpieczny, więc zignoruje rzucone wyjątki, które minęły później na ścieżce, w zasadzie masz efekty uboczne, których nie możesz łatwo zaobserwować.

Zauważ, że returnwczesne ing jest również bardzo częste w callbackach:

function divide(nom, denom, cb){
     if(denom === 0){
         cb(Error("Cannot divide by zero");
         return; // unlike with promises, missing the return here is a mistake
     }
     cb(null, nom / denom); // this will divide by zero. Since it's a callback.
} 

Tak więc, chociaż jest to dobra praktyka w obietnicach, jest wymagana w przypadku wywołań zwrotnych. Kilka uwag na temat Twojego kodu:

  • Twój przypadek użycia jest hipotetyczny, nie używaj obietnic przy akcjach synchronicznych.
  • Konstruktor obietnicy ignoruje zwracane wartości. Niektóre biblioteki będą ostrzegać, jeśli zwrócisz nieokreśloną wartość, aby ostrzec Cię przed błędem powrotu. Większość nie jest taka mądra.
  • Konstruktor obietnicy jest bezpieczny, przekształci wyjątki w odrzucenia, ale jak zauważyli inni - obietnica rozwiązuje się raz.

4

W wielu przypadkach można osobno zweryfikować parametry i natychmiast zwrócić odrzuconą obietnicę za pomocą Promise.reject (powód) .

function divide2(numerator, denominator) {
  if (denominator === 0) {
    return Promise.reject("Cannot divide by 0");
  }
  
  return new Promise((resolve, reject) => {
    resolve(numerator / denominator);
  });
}


divide2(4, 0).then((result) => console.log(result), (error) => console.log(error));

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.