try / catch bloki z async / await

116

Zagłębiam się w funkcję async / await w węźle 7 i wciąż natknę się na taki kod

function getQuote() {
  let quote = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.";
  return quote;
}

async function main() {
  try {
    var quote = await getQuote();
    console.log(quote);
  } catch (error) {
    console.error(error);
  }
}

main();

Wydaje się, że jest to jedyna możliwość rozwiązania / odrzucenia lub zwrotu / wyrzucenia z async / await, jednak wersja 8 nie optymalizuje kodu w blokach try / catch ?!

Czy są alternatywy?

Patrick
źródło
Co oznacza „rzut po oczekiwaniu nie jest skuteczny”? Jeśli to błędy? Jeśli nie przyniesie oczekiwanego wyniku? Możesz ponownie wrzucić do bloku catch.
DevDig
afaik v8 optymalizuje try / catch, oświadczenie o rzucie jest powolne
Tamas Hegedus
1
Nadal nie rozumiem pytania. Używasz starych łańcuchów obietnic, ale nie sądzę, żeby to było szybsze. Więc martwisz się o wydajność try-catch? Więc co to ma wspólnego z async awit?
Tamas Hegedus
Sprawdź moją odpowiedź Próbowałem uzyskać czystsze podejście
zardilior
Tutaj możesz to zrobić stackoverflow.com/a/61833084/6482248 Wygląda czyściej
Prathamesh Więcej

Odpowiedzi:

133

Alternatywy

Alternatywa dla tego:

async function main() {
  try {
    var quote = await getQuote();
    console.log(quote);
  } catch (error) {
    console.error(error);
  }
}

byłoby coś takiego, używając wyraźnie obietnic:

function main() {
  getQuote().then((quote) => {
    console.log(quote);
  }).catch((error) => {
    console.error(error);
  });
}

lub coś takiego, używając stylu przekazywania kontynuacji:

function main() {
  getQuote((error, quote) => {
    if (error) {
      console.error(error);
    } else {
      console.log(quote);
    }
  });
}

Oryginalny przykład

To, co robi twój oryginalny kod, to wstrzymanie wykonania i oczekiwanie na getQuote()rozliczenie zwróconej przez niego obietnicy . Następnie kontynuuje wykonywanie i zapisuje zwróconą wartość do, var quotea następnie drukuje ją, jeśli obietnica została rozwiązana, lub zgłasza wyjątek i uruchamia blok catch, który wyświetla błąd, jeśli obietnica została odrzucona.

Możesz zrobić to samo używając Promise API bezpośrednio, jak w drugim przykładzie.

Występ

Teraz do wykonania. Przetestujmy to!

Właśnie napisałem ten kod - f1()podaje 1jako wartość zwracaną, f2()wyrzuca 1jako wyjątek:

function f1() {
  return 1;
}

function f2() {
  throw 1;
}

Teraz wywołajmy ten sam kod milion razy, najpierw f1():

var sum = 0;
for (var i = 0; i < 1e6; i++) {
  try {
    sum += f1();
  } catch (e) {
    sum += e;
  }
}
console.log(sum);

A potem przejdźmy f1()do f2():

var sum = 0;
for (var i = 0; i < 1e6; i++) {
  try {
    sum += f2();
  } catch (e) {
    sum += e;
  }
}
console.log(sum);

Oto wynik, który otrzymałem za f1:

$ time node throw-test.js 
1000000

real    0m0.073s
user    0m0.070s
sys     0m0.004s

Za to mam f2:

$ time node throw-test.js 
1000000

real    0m0.632s
user    0m0.629s
sys     0m0.004s

Wygląda na to, że możesz zrobić około 2 milionów rzutów na sekundę w jednym procesie jednowątkowym. Jeśli robisz więcej niż to, być może będziesz musiał się tym martwić.

Podsumowanie

Nie martwiłbym się takimi rzeczami w Node. Jeśli takie rzeczy są często używane, zostaną ostatecznie zoptymalizowane przez zespoły V8, SpiderMonkey lub Chakra i wszyscy będą podążać - nie jest tak, że nie jest zoptymalizowany jako zasada, po prostu nie stanowi problemu.

Nawet jeśli nie jest zoptymalizowany, nadal argumentowałbym, że jeśli maksymalizujesz swój procesor w Node, prawdopodobnie powinieneś napisać chrupanie liczb w C - między innymi do tego służą natywne dodatki. A może rzeczy takie jak node.native byłyby lepiej dostosowane do tego zadania niż Node.js.

Zastanawiam się, jaki byłby przypadek użycia, który wymaga rzucania tak wielu wyjątków. Zwykle zgłoszenie wyjątku zamiast zwracania wartości jest, no cóż, wyjątkiem.

rsp
źródło
Wiem, że kod można łatwo napisać za pomocą Obietnic, jak wspomniano, widziałem go na różnych przykładach, dlatego pytam. Posiadanie pojedynczej operacji w ramach try / catch może nie stanowić problemu, ale może nim być wiele funkcji async / await z dalszą logiką aplikacji.
Patrick,
4
@Patrick „może być” i „będzie” to różnica między spekulacją a faktycznym testowaniem. Przetestowałem to dla pojedynczego stwierdzenia, ponieważ o to właśnie chodziło w twoim pytaniu, ale możesz łatwo przekonwertować moje przykłady, aby przetestować wiele zdań. Udostępniłem również kilka innych opcji pisania kodu asynchronicznego, o które również pytałeś. Jeśli odpowiada na Twoje pytanie, możesz rozważyć przyjęcie odpowiedzi . Podsumowując: oczywiście wyjątki są wolniejsze niż zwroty, ale ich użycie powinno być wyjątkiem.
rsp
1
Zgłoszenie wyjątku rzeczywiście powinno być wyjątkiem. Biorąc to pod uwagę, kod jest niezoptymalizowany, niezależnie od tego, czy zgłosisz wyjątek, czy nie. Uderzenie w wydajność pochodzi z użycia try catch, a nie z wyrzucenia wyjątku. Chociaż liczby są małe, według twoich testów jest prawie 10 razy wolniejsze, co nie jest bez znaczenia.
Nepoxx,
22

Alternatywa podobna do obsługi błędów w Golangu

Ponieważ async / await używa obietnic pod maską, możesz napisać małą funkcję użytkową, taką jak ta:

export function catchEm(promise) {
  return promise.then(data => [null, data])
    .catch(err => [err]);
}

Następnie zaimportuj go, gdy chcesz wychwycić jakieś błędy, i zawiń swoją funkcję asynchroniczną, która zwraca z nią obietnicę.

import catchEm from 'utility';

async performAsyncWork() {
  const [err, data] = await catchEm(asyncFunction(arg1, arg2));
  if (err) {
    // handle errors
  } else {
    // use data
  }
}
Steve Banton
źródło
Utworzyłem pakiet NPM, który robi dokładnie to, co powyżej - npmjs.com/package/@simmo/task
Mike
2
@Mike Być może ponownie wymyślasz koło - istnieje już popularny pakiet, który robi dokładnie to: npmjs.com/package/await-to-js
Jakub
21

Alternatywą dla bloku try-catch jest await-to-js lib. Często go używam. Na przykład:

import to from 'await-to-js';

async function main(callback) {
    const [err,quote] = await to(getQuote());
    
    if(err || !quote) return callback(new Error('No Quote found'));

    callback(null,quote);

}

Ta składnia jest znacznie czystsza w porównaniu do try-catch.

Pulkit chadha
źródło
Wypróbowałem to i pokochałem. Czysty i czytelny kod kosztem instalacji nowego modułu. Ale jeśli planujesz napisać wiele funkcji asynchronicznych, muszę powiedzieć, że to świetny dodatek! Dzięki
filipbarak
15
async function main() {
  var getQuoteError
  var quote = await getQuote().catch(err => { getQuoteError = err }

  if (getQuoteError) return console.error(err)

  console.log(quote)
}

Alternatywnie, zamiast deklarować możliwą zmienną, aby zawierała błąd na górze, możesz zrobić

if (quote instanceof Error) {
  // ...
}

Chociaż to nie zadziała, jeśli zostanie wyrzucony błąd typu TypeError lub Reference. Możesz jednak upewnić się, że jest to zwykły błąd

async function main() {
  var quote = await getQuote().catch(err => {
    console.error(err)      

    return new Error('Error getting quote')
  })

  if (quote instanceOf Error) return quote // get out of here or do whatever

  console.log(quote)
}

Preferuję to zawijanie wszystkiego w duży blok próbny, w którym jest tworzonych wiele obietnic, co może utrudnić obsługę błędu specjalnie dla obietnicy, która go utworzyła. Alternatywą jest wiele bloków try-catch, które uważam za równie kłopotliwe

Tony
źródło
8

Czystsza alternatywa byłaby następująca:

Ze względu na fakt, że każda funkcja asynchroniczna jest technicznie obietnicą

Możesz dodawać zaczepy do funkcji, wywołując je za pomocą await

async function a(){
    let error;

    // log the error on the parent
    await b().catch((err)=>console.log('b.failed'))

    // change an error variable
    await c().catch((err)=>{error=true; console.log(err)})

    // return whatever you want
    return error ? d() : null;
}
a().catch(()=>console.log('main program failed'))

Nie ma potrzeby próbowania złapania, ponieważ wszystkie błędy obietnic są obsługiwane, a nie masz błędów w kodzie, możesz to pominąć w nadrzędnym!

Powiedzmy, że pracujesz z mongodb, jeśli wystąpi błąd, możesz go obsłużyć w funkcji wywołującej go niż tworzenie opakowań lub używanie try catch.

zardilior
źródło
Masz 3 funkcje. Jeden pobierający wartości i wychwytujący błąd, drugi zwracany, jeśli nie ma błędu, a na końcu wywołanie pierwszej funkcji z wywołaniem zwrotnym, aby sprawdzić, czy ta zwróciła błąd. Wszystko to rozwiązuje pojedynczy blok „obietnica”. Then (cb) .catch (cb) lub trycatch.
Chief koshi
@Chiefkoshi Jak widać, pojedynczy haczyk nie zadziałał, ponieważ błąd jest obsługiwany inaczej we wszystkich trzech przypadkach. Jeśli pierwsza nie powiedzie się, zwraca d (), jeśli druga nie powiedzie się, zwraca null, jeśli ostatnia zawiedzie, zostanie wyświetlony inny komunikat o błędzie. Pytanie dotyczy obsługi błędów podczas korzystania z await. Więc to jest również odpowiedź. Wszystkie powinny zostać wykonane, jeśli którykolwiek zawiedzie. Wypróbuj bloki catch wymagałyby trzech z nich w tym konkretnym przykładzie, który nie jest czystszy
zardilior
1
Pytanie nie dotyczy realizacji po nieudanych obietnicach. Tutaj czekasz na B, a następnie uruchamiasz C i zwracasz D, jeśli popełnili błąd. Jak to jest czystsze? C musi czekać na B, ale są od siebie niezależne. Nie widzę powodu, dla którego mieliby być razem w grupie A, skoro są niezależni. Gdyby były od siebie zależne, chciałbyś zatrzymać wykonywanie C, jeśli B zawiedzie, zadanie .then.catch lub try-catch. Zakładam, że nic nie zwracają i wykonują asynchroniczne akcje całkowicie niezwiązane z A. Dlaczego są wywoływane z async await?
Chief koshi
Pytanie dotyczy alternatywnych sposobów na wypróbowanie bloków catch w celu obsługi błędów podczas korzystania z async / await. Przykład tutaj ma być opisowy i jest niczym innym jak przykładem. Pokazuje indywidualną obsługę niezależnych operacji w sposób sekwencyjny, co zwykle jest używane w przypadku async / await. Dlaczego są wywoływane z async await, to tylko pokazanie, jak można sobie z tym poradzić. Jest bardziej opisowy niż uzasadniony.
zardilior
2

Chciałbym to zrobić w ten sposób :)

const sthError = () => Promise.reject('sth error');

const test = opts => {
  return (async () => {

    // do sth
    await sthError();
    return 'ok';

  })().catch(err => {
    console.error(err); // error will be catched there 
  });
};

test().then(ret => {
  console.log(ret);
});

Jest to podobne do obsługi błędów w programie co

const test = opts => {
  return co(function*() {

    // do sth
    yield sthError();
    return 'ok';

  }).catch(err => {
    console.error(err);
  });
};
Cooper Hsiung
źródło
Kod nie jest zbyt jasny człowieku, wygląda jednak interesująco, czy mógłbyś edytować?
zardilior
Szkoda, że ​​w tej odpowiedzi nie ma wyjaśnienia, ponieważ w rzeczywistości pokazuje ona świetny sposób na uniknięcie próbowania łapania każdej przypisanej stałej await!
Jim
0

catchz doświadczenia wiem, że takie postępowanie jest niebezpieczne. Każdy błąd wyrzucony w całym stosie zostanie wychwycony, a nie tylko błąd z tej obietnicy (która prawdopodobnie nie jest tym, czego chcesz).

Drugim argumentem obietnicy jest już wywołanie zwrotne dotyczące odrzucenia / niepowodzenia. Zamiast tego lepiej i bezpieczniej jest z tego korzystać.

Oto bezpieczna dla maszynisty jednowierszowa, którą napisałem, aby sobie z tym poradzić:

function wait<R, E>(promise: Promise<R>): [R | null, E | null] {
  return (promise.then((data: R) => [data, null], (err: E) => [null, err]) as any) as [R, E];
}

// Usage
const [currUser, currUserError] = await wait<GetCurrentUser_user, GetCurrentUser_errors>(
  apiClient.getCurrentUser()
);
sarink
źródło