Obietnice JavaScript - odrzucanie kontra rzut

384

Przeczytałem kilka artykułów na ten temat, ale nadal nie jest dla mnie jasne, czy istnieje różnica między Promise.rejectzgłaszaniem błędu. Na przykład,

Korzystanie z Promise.reject

return asyncIsPermitted()
    .then(function(result) {
        if (result === true) {
            return true;
        }
        else {
            return Promise.reject(new PermissionDenied());
        }
    });

Za pomocą rzutu

return asyncIsPermitted()
    .then(function(result) {
        if (result === true) {
            return true;
        }
        else {
            throw new PermissionDenied();
        }
    });

Wolę używać throwpo prostu dlatego, że jest krótszy, ale zastanawiałem się, czy jest jakaś przewaga jednego nad drugim.

Naresh
źródło
9
Obie metody dają dokładnie taką samą odpowiedź. Przewodnik .then()przechwytuje zgłoszony wyjątek i automatycznie zamienia go w odrzuconą obietnicę. Ponieważ przeczytałem, że zgłoszone wyjątki nie są szczególnie szybkie do wykonania, zgaduję, że zwrócenie odrzuconej obietnicy może być nieco szybsze do wykonania, ale trzeba by było opracować test w wielu nowoczesnych przeglądarkach, gdyby to było ważne. Osobiście używam, throwponieważ lubię czytelność.
jfriend00
@webduvet nie z obietnicami - są one zaprojektowane do pracy z rzutem.
dołącza
15
Jednym minusem throwjest to, że nie spowodowałoby to odrzucenia obietnicy, gdyby została wyrzucona z asynchronicznego wywołania zwrotnego, takiego jak setTimeout. jsfiddle.net/m07van33 @Blondie Twoja odpowiedź była poprawna.
Kevin B
@joews to nie znaczy, że jest dobre;)
webduvet
1
Ach, prawda. Wyjaśnienie mojego komentarza brzmiałoby: „gdyby zostało wyrzucone z asynchronicznego wywołania zwrotnego, które nie zostało obiecane . Wiedziałem, że jest wyjątek, po prostu nie pamiętałem, co to było. Ja też wolę używać rzucania po prostu dlatego, że uważam, że jest bardziej czytelny i pozwala mi pominąć rejectgo na liście parametrów.
Kevin B,

Odpowiedzi:

344

Nie ma żadnej korzyści z używania jednego kontra drugiego, ale istnieje konkretny przypadek, w którym thrownie zadziała. Te przypadki można jednak naprawić.

Za każdym razem, gdy znajdziesz się w ramach oddzwaniania od obietnicy, możesz z niego skorzystać throw. Jeśli jednak korzystasz z innego asynchronicznego połączenia zwrotnego, musisz go użyć reject.

Na przykład nie spowoduje to wyłapania:

new Promise(function() {
  setTimeout(function() {
    throw 'or nah';
    // return Promise.reject('or nah'); also won't work
  }, 1000);
}).catch(function(e) {
  console.log(e); // doesn't happen
});

Zamiast tego masz nierozwiązaną obietnicę i niewykorzystany wyjątek. Jest to przypadek, w którym chciałbyś zamiast tego użyć reject. Można to jednak naprawić na dwa sposoby.

  1. używając funkcji odrzucania oryginalnej Obietnicy w ramach limitu czasu:

new Promise(function(resolve, reject) {
  setTimeout(function() {
    reject('or nah');
  }, 1000);
}).catch(function(e) {
  console.log(e); // works!
});

  1. obiecując limit czasu:

function timeout(duration) { // Thanks joews
  return new Promise(function(resolve) {
    setTimeout(resolve, duration);
  });
}

timeout(1000).then(function() {
  throw 'worky!';
  // return Promise.reject('worky'); also works
}).catch(function(e) {
  console.log(e); // 'worky!'
});

Kevin B.
źródło
54
Warto wspomnieć, że miejsca wewnątrz nieudzielonego wywołania zwrotnego asynchronicznego, których nie możesz użyć throw error, nie możesz również użyć, o return Promise.reject(err)co OP poprosił nas o porównanie. Zasadniczo dlatego nie należy umieszczać wywołań asynchronicznych w obietnicach. Promuj wszystko, co asynchronizuje, a wtedy nie masz tych ograniczeń.
jfriend00
9
„Jeśli jednak korzystasz z innego rodzaju oddzwaniania”, naprawdę powinno to brzmieć „Jeśli jednak korzystasz z innego rodzaju asynchronicznego połączenia zwrotnego”. Oddzwanianie może być synchroniczne (np. Z Array#forEach) i z tymi, rzucanie w nich działałoby.
Félix Saparelli
2
@KevinB czytając te linie „istnieje konkretny przypadek, w którym rzut nie działa”. oraz „Za każdym razem, gdy znajdujesz się w ramach oddzwaniania od obietnicy, możesz użyć funkcji rzucania. Jeśli jednak korzystasz z innego asynchronicznego oddzwaniania, musisz użyć funkcji odrzucania”. Mam wrażenie, że przykładowe fragmenty pokażą przypadki, w których thrownie będą działać, a zamiast tego Promise.rejectsą lepszym wyborem. Jednak na fragmenty nie ma wpływu żadna z tych dwóch opcji i dają taki sam wynik niezależnie od tego, co wybierzesz. Czy coś brakuje?
Anshul
2
tak. jeśli użyjesz rzucania w setTimeout, catch nie zostanie wywołany. musisz użyć tego, rejectco zostało przekazane do new Promise(fn)oddzwaniania.
Kevin B
2
@KevinB dzięki za pozostanie z nami. Przykład podany przez OP wspomina, że ​​szczególnie chciał porównać return Promise.reject()i throw. Nie wspomina o rejectwywołaniu zwrotnym podanym w new Promise(function(resolve, reject))konstrukcji. Więc chociaż twoje dwa fragmenty słusznie pokazują, kiedy powinieneś użyć wywołania zwrotnego rozstrzygania, pytanie OP nie było takie.
Anshul
200

Innym ważnym faktem jest to, że reject() NIE przerywa przepływu sterowania, jak returnrobi to instrukcja. W przeciwieństwie do throwtego kończy sterowanie przepływem.

Przykład:

new Promise((resolve, reject) => {
  throw "err";
  console.log("NEVER REACHED");
})
.then(() => console.log("RESOLVED"))
.catch(() => console.log("REJECTED"));

vs

new Promise((resolve, reject) => {
  reject(); // resolve() behaves similarly
  console.log("ALWAYS REACHED"); // "REJECTED" will print AFTER this
})
.then(() => console.log("RESOLVED"))
.catch(() => console.log("REJECTED"));

lukyer
źródło
50
Cóż, to prawda, ale porównanie jest trudne. Ponieważ zwykle powinieneś odrzucić odrzuconą obietnicę, pisząc return reject(), więc następny wiersz się nie uruchomi.
AZ.
7
Dlaczego chcesz to zwrócić?
lukyer
31
W tym przypadku return reject()jest to po prostu skrót, reject(); returntzn. To, czego chcesz, to zakończyć przepływ. Zwracana wartość modułu wykonującego (funkcja przekazana do new Promise) nie jest używana, więc jest to bezpieczne.
Félix Saparelli
47

Tak, największą różnicą jest to, że odrzucenie to funkcja zwrotna, która jest wykonywana po odrzuceniu obietnicy, podczas gdy rzut nie może być użyty asynchronicznie. Jeśli zdecydujesz się użyć odrzucenia, twój kod będzie nadal działał normalnie w sposób asynchroniczny, podczas gdy rzut będzie priorytetem dla ukończenia funkcji resolvera (ta funkcja uruchomi się natychmiast).

Przykład, który widziałem, który pomógł mi wyjaśnić ten problem, to to, że możesz ustawić funkcję Limit czasu z odrzuceniem, na przykład:

new Promise(_, reject) {
 setTimeout(reject, 3000);
});

Powyższe nie byłoby możliwe do napisania rzutem.

W twoim małym przykładzie różnica w nierozróżnialnym, ale w przypadku bardziej skomplikowanej koncepcji asynchronicznej różnica między nimi może być drastyczna.

Blondie
źródło
1
To brzmi jak kluczowa koncepcja, ale nie rozumiem tego jak napisano. Sądzę, że wciąż jest zbyt nowy w stosunku do obietnic.
David Spector,
43

TLDR: Funkcja jest trudna w użyciu, gdy czasami zwraca obietnicę, a czasem zgłasza wyjątek. Pisząc funkcję asynchroniczną, wolisz zasygnalizować błąd, zwracając odrzuconą obietnicę

Twój konkretny przykład zaciemnia niektóre ważne różnice między nimi:

Ponieważ zajmujesz się błędami w łańcuchu obietnic, zgłoszone wyjątki są automatycznie konwertowane na odrzucone obietnice. To może wyjaśniać, dlaczego wydają się one wymienne - nie są.

Rozważ poniższą sytuację:

checkCredentials = () => {
    let idToken = localStorage.getItem('some token');
    if ( idToken ) {
      return fetch(`https://someValidateEndpoint`, {
        headers: {
          Authorization: `Bearer ${idToken}`
        }
      })
    } else {
      throw new Error('No Token Found In Local Storage')
    }
  }

Byłby to anty-wzorzec, ponieważ musiałbyś wtedy obsługiwać zarówno przypadki błędów asynchronizacji, jak i synchronizacji. Może wyglądać mniej więcej tak:

try {
  function onFulfilled() { ... do the rest of your logic }
  function onRejected() { // handle async failure - like network timeout }
  checkCredentials(x).then(onFulfilled, onRejected);
} catch (e) {
  // Error('No Token Found In Local Storage')
  // handle synchronous failure
} 

Nie jest dobrze, a oto gdzie dokładnie Promise.reject(dostępny w skali globalnej) przychodzi na ratunek i skutecznie się odróżnia throw. Refaktor staje się teraz:

checkCredentials = () => {
  let idToken = localStorage.getItem('some_token');
  if (!idToken) {
    return Promise.reject('No Token Found In Local Storage')
  }
  return fetch(`https://someValidateEndpoint`, {
    headers: {
      Authorization: `Bearer ${idToken}`
    }
  })
}

To pozwala teraz używać tylko jednego w catch()przypadku awarii sieci i synchronicznego sprawdzania błędów pod kątem braku tokenów:

checkCredentials()
      .catch((error) => if ( error == 'No Token' ) {
      // do no token modal
      } else if ( error === 400 ) {
      // do not authorized modal. etc.
      }
Maxwell
źródło
1
Jednak przykład Op zawsze zwraca obietnicę. Pytanie dotyczy tego, czy powinieneś użyć Promise.rejectlub throwkiedy chcesz zwrócić odrzuconą obietnicę (obietnicę, która przejdzie do następnej .catch()).
Marcos Pereira,
@maxwell - lubię cię przykład. W tym samym czasie, jeśli podczas pobierania dodasz haczyk, a w nim rzucisz wyjątek, możesz bezpiecznie użyć try ... catch ... Nie ma idealnego świata na wyjściu, ale myślę, że używając jednego pojedynczy wzór ma sens, a łączenie wzorów nie jest bezpieczne (dostosowane do wzoru w porównaniu z analogią anty-wzorcową).
user3053247,
1
Doskonała odpowiedź, ale widzę tutaj wadę - ten wzór zakłada, że ​​wszystkie błędy są obsługiwane przez zwrócenie Promise.reject - co dzieje się ze wszystkimi nieoczekiwanymi błędami, które mogą zostać wyrzucone z checkCredentials ()?
chenop
1
Tak, masz rację @chenop - aby złapać te nieoczekiwane błędy, musisz jeszcze zawinąć w try / catch
maxwell
Nie rozumiem przypadku @ Maxwella. Czy nie mógłbyś tak po prostu skonstruować go w taki sposób checkCredentials(x).then(onFulfilled).catch(e) {}, aby posiadał catchzarówno przypadek odrzucenia, jak i przypadek błędu?
Ben Wheeler,
5

Przykład do wypróbowania. Wystarczy zmienić isVersionThrow na false, aby użyć odrzucenia zamiast rzutu.

const isVersionThrow = true

class TestClass {
  async testFunction () {
    if (isVersionThrow) {
      console.log('Throw version')
      throw new Error('Fail!')
    } else {
      console.log('Reject version')
      return new Promise((resolve, reject) => {
        reject(new Error('Fail!'))
      })
    }
  }
}

const test = async () => {
  const test = new TestClass()
  try {
    var response = await test.testFunction()
    return response 
  } catch (error) {
    console.log('ERROR RETURNED')
    throw error 
  }  
}

test()
.then(result => {
  console.log('result: ' + result)
})
.catch(error => {
  console.log('error: ' + error)
})

Chris Livdahl
źródło