Jak mogę używać async / await na najwyższym poziomie?

182

Przeszukiwałem async/ awaiti po przejrzeniu kilku artykułów postanowiłem sam przetestować. Jednak wydaje mi się, że nie mogę zrozumieć, dlaczego to nie działa:

async function main() {  
    var value = await Promise.resolve('Hey there');
    console.log('inside: ' + value);
    return value;
}

var text = main();  
console.log('outside: ' + text);

Konsola wyświetla następujące informacje (węzeł v8.6.0):

> na zewnątrz: [obietnica obiektu]

> wewnątrz: Hej

Dlaczego komunikat dziennika wewnątrz funkcji jest później wykonywany? Myślałem, że powodem async/ awaitzostał utworzony w celu wykonania synchronicznego wykonywania przy użyciu zadań asynchronicznych.

Czy istnieje sposób, aby użyć wartości zwróconej w funkcji bez użycia .then()after main()?

Felipe
źródło
4
Nie, tylko maszyny czasu mogą synchronizować kod asynchroniczny. awaitto nic innego jak cukier dla thenskładni obietnicy .
Bergi
Dlaczego main zwraca wartość? Jeśli tak, prawdopodobnie nie jest to punkt wejścia i musi zostać wywołane przez inną funkcję (np. Async IIFE).
Estus Flask
@estus to była tylko szybka nazwa funkcji, gdy testowałem rzeczy w węźle, niekoniecznie reprezentatywną dla programumain
Felipe
2
FYI, async/awaitjest częścią ES2017, a nie ES7 (ES2016)
Felix Kling

Odpowiedzi:

267

Nie potrafię zrozumieć, dlaczego to nie działa.

Ponieważ mainzwraca obietnicę; wszystkie asyncfunkcje robią.

Na najwyższym poziomie musisz:

  1. Użyj funkcji najwyższego poziomu, asyncktóra nigdy nie odrzuca (chyba że chcesz uzyskać błędy „nieobsługiwanego odrzucenia”) lub

  2. Użyj theni catch, lub

  3. (Wkrótce!) Użyj najwyższego poziomuawait , propozycji, która osiągnęła etap 3 w procesie, który pozwala na użycie najwyższego poziomu awaitw module.

# 1 - asyncFunkcja najwyższego poziomu, która nigdy nie odrzuca

(async () => {
    try {
        var text = await main();
        console.log(text);
    } catch (e) {
        // Deal with the fact the chain failed
    }
})();

Zwróć uwagę na catch; Państwo musi obsługiwać odrzucenia obietnica / async wyjątki, gdyż nic innego nie będzie; nie masz rozmówcy, któremu mógłbyś je przekazać. Jeśli wolisz, możesz to zrobić na wyniku wywołania go za pomocą catchfunkcji (zamiast try/ catchsyntax):

(async () => {
    var text = await main();
    console.log(text);
})().catch(e => {
    // Deal with the fact the chain failed
});

... który jest nieco bardziej zwięzły (podoba mi się z tego powodu).

Lub, oczywiście, nie obsługuj błędów i po prostu zezwalaj na błąd „nieobsługiwanego odrzucenia”.

# 2 - thenicatch

main()
    .then(text => {
        console.log(text);
    })
    .catch(err => {
        // Deal with the fact the chain failed
    });

Procedura catchobsługi zostanie wywołana, jeśli wystąpią błędy w łańcuchu lub w twoim thenmodule obsługi. (Upewnij się, że catchprogram obsługi nie zgłasza błędów, ponieważ nic nie zostało zarejestrowane do ich obsługi).

Lub oba argumenty za then:

main().then(
    text => {
        console.log(text);
    },
    err => {
        // Deal with the fact the chain failed
    }
);

Ponownie zauważ, że rejestrujemy moduł obsługi odrzucania. Ale w tej formie upewnij się, że żaden z twoich plikówthen wywołań zwrotnych nie generuje żadnych błędów, nic nie jest zarejestrowane, aby je obsłużyć.

# 3 najwyższy poziom awaitw module

Nie można używać awaitna najwyższym poziomie skryptu niebędącego modułem, ale propozycja najwyższego poziomuawait ( Etap 3 ) pozwala na użycie go na najwyższym poziomie modułu. Jest to podobne do używania asyncopakowania funkcji najwyższego poziomu (# 1 powyżej), ponieważ nie chcesz, aby kod najwyższego poziomu odrzucał (zgłaszał błąd), ponieważ spowoduje to nieobsłużony błąd odrzucenia. Więc jeśli nie chcesz mieć tego nieobsługiwanego odrzucenia, gdy coś pójdzie nie tak, jak w przypadku # 1, chciałbyś opakować swój kod w moduł obsługi błędów:

// In a module, once the top-level `await` proposal lands
try {
    var text = await main();
    console.log(text);
} catch (e) {
    // Deal with the fact the chain failed
}

Zwróć uwagę, że jeśli to zrobisz, każdy moduł, który zaimportuje z Twojego modułu, będzie czekał, aż obietnica, którą tworzysz, zostanie awaitspełniona; kiedy moduł używający najwyższego poziomu awaitjest oceniany, w zasadzie zwraca obietnicę do modułu ładującego (podobnie jak asyncfunkcja), który czeka, aż ta obietnica zostanie rozliczona, przed oceną treści wszelkich modułów, które od niej zależą.

TJ Crowder
źródło
Myślenie o tym jak o obietnicy wyjaśnia teraz, dlaczego funkcja natychmiast powraca. Eksperymentowałem z utworzeniem anonimowej funkcji asynchronicznej najwyższego poziomu i otrzymałem wyniki, które mają teraz sens
Felipe
2
@Felipe: Tak, async/ awaitsą cukrem syntaktycznym wokół obietnic (dobry rodzaj cukru :-)). Nie myślisz o tym tylko jako o zwrocie obietnicy; faktycznie tak. ( Szczegóły )
TJ Crowder
1
@LukeMcGregor - pokazałem oba powyżej, z asyncopcją all- first. W przypadku funkcji najwyższego poziomu widzę to tak czy inaczej (głównie z powodu dwóch poziomów wcięć w asyncwersji).
TJ Crowder
3
@Felipe - Zaktualizowałem odpowiedź teraz, gdy awaitpropozycja najwyższego poziomu osiągnęła etap 3. :-)
TJ Crowder
1
@SurajShrestha - Nie. Ale to nie jest problem, że tak nie jest. :-)
TJ Crowder
7

Najwyższy poziomawait przeszedł do etapu 3, więc odpowiedź na Twoje pytanie Jak mogę używać async / await na najwyższym poziomie? to po prostu dodać awaitwywołanie do main():

async function main() {  
    var value = await Promise.resolve('Hey there');
    console.log('inside: ' + value);
    return value;
}

var text = await main();  
console.log('outside: ' + text)

Lub tylko:

const text = await Promise.resolve('Hey there');
console.log('outside: ' + text)

Pamiętaj, że nadal jest dostępny tylko w [email protected] .

Jeśli używasz TypeScript , wylądował w 3.8 .

v8 dodał wsparcie w modułach.

Jest również obsługiwany przez Deno (jak skomentował gonzalo-bahamondez).

Taro
źródło
Całkiem fajne. Czy mamy jakiś plan wdrożenia Node
Felipe,
Nie wiem, ale jest bardzo prawdopodobne, że wkrótce zobaczymy implementację TypeScript i Babel. Zespół TypeScript ma politykę wdrażania funkcji języka stage-3, a wtyczka Babel jest zwykle tworzona jako część procesu TC39 w celu przetestowania propozycji. Zobacz github.com/Microsoft/TypeScript/issues/…
Taro,
Jest również dostępny w deno (tylko js, ​​maszynopis nadal go nie obsługuje github.com/microsoft/TypeScript/issues/25988 ) deno.land patrz deno.news/issues/… .
Gonzalo Bahamondez
SyntaxError:
await
4

Rzeczywistym rozwiązaniem tego problemu jest podejście do tego inaczej.

Prawdopodobnie Twoim celem jest jakaś inicjalizacja, która zwykle ma miejsce na najwyższym poziomie aplikacji.

Rozwiązaniem jest zapewnienie, że na najwyższym poziomie aplikacji istnieje tylko jedna instrukcja JavaScript. Jeśli masz tylko jedną instrukcję u góry aplikacji, możesz swobodnie używać async / await w każdym innym miejscu (oczywiście z zastrzeżeniem normalnych reguł składni)

Innymi słowy, zawiń cały najwyższy poziom w funkcję, aby nie był już najwyższym poziomem i to rozwiązuje pytanie, jak uruchomić async / await na najwyższym poziomie aplikacji - tego nie robisz.

Tak powinien wyglądać najwyższy poziom Twojej aplikacji:

import {application} from './server'

application();
Książę Dougal
źródło
1
Masz rację, że moim celem jest inicjalizacja. Rzeczy takie jak połączenia z bazami danych, pobieranie danych itp. W niektórych przypadkach konieczne było uzyskanie danych użytkownika przed kontynuowaniem pozostałej części aplikacji. Zasadniczo proponujesz, aby application()był asynchroniczny?
Felipe
1
Nie, mówię tylko, że jeśli w katalogu głównym aplikacji znajduje się tylko jedna instrukcja JavaScript, problem zniknął - instrukcja najwyższego poziomu, jak pokazano, nie jest asynchroniczna. Problem polega na tym, że nie jest możliwe użycie async na najwyższym poziomie - nie możesz czekać na oczekiwanie na tym poziomie - dlatego jeśli jest tylko jedno polecenie na najwyższym poziomie, ominiesz ten problem. Twój kod asynchroniczny inicjalizacji jest teraz wyłączony w pewnym zaimportowanym kodzie i dlatego asynchronizacja będzie działać dobrze i możesz zainicjować wszystko na początku aplikacji.
Duke Dougal
1
KOREKTA - aplikacja JEST funkcją asynchroniczną.
Duke Dougal
4
Nie jest jasne, przepraszam. Chodzi o to, że zwykle na najwyższym poziomie funkcja asynchroniczna nie czeka ... JavaScript przechodzi bezpośrednio do następnej instrukcji, więc nie możesz być pewien, że kod inicjujący został zakończony. Jeśli u góry aplikacji znajduje się tylko jedno stwierdzenie, to po prostu nie ma to znaczenia.
Duke Dougal
3

Aby podać dodatkowe informacje na temat aktualnych odpowiedzi:

Zawartość pliku node.js pliku jest obecnie łączona w sposób podobny do łańcucha, aby utworzyć funkcji.

Na przykład, jeśli masz plik test.js:

// Amazing test file!
console.log('Test!');

Następnie node.jspotajemnie połączy funkcję, która wygląda następująco:

function(require, __dirname, ... a bunch more top-level properties) {
  // Amazing test file!
  console.log('test!');
}

Najważniejszą rzeczą, na którą należy zwrócić uwagę, jest to, że wynikowa funkcja NIE jest funkcją asynchroniczną. Nie możesz więc użyć tego terminuawait bezpośrednio w nim!

Ale powiedz, że musisz pracować z obietnicami w tym pliku, istnieją dwie możliwe metody:

  1. Nie używaj await bezpośrednio w funkcji
  2. Nie używaj await

Opcja 1 wymaga od nas stworzenia nowego zakresu (a TEN zakres może być async, ponieważ mamy nad nim kontrolę):

// Amazing test file!
// Create a new async function (a new scope) and immediately call it!
(async () => {
  await new Promise(...);
  console.log('Test!');
})();

Opcja 2 wymaga od nas użycia zorientowanego obiektowo interfejsu API obietnic (mniej ładny, ale równie funkcjonalny paradygmat pracy z obietnicami)

// Amazing test file!
// Create some sort of promise...
let myPromise = new Promise(...);

// Now use the object-oriented API
myPromise.then(() => console.log('Test!'));

Osobiście mam nadzieję, że jeśli będzie to wykonalne, node.js domyślnie połączy kod w asyncfunkcję. To by pozbyło się tego bólu głowy.

Gershom
źródło
0

Oczekiwanie na najwyższym poziomie to funkcja nadchodzącego standardu EcmaScript. Obecnie możesz zacząć używać go z TypeScript 3.8 (obecnie w wersji RC).

Jak zainstalować TypeScript 3.8

Możesz rozpocząć korzystanie z TypeScript 3.8, instalując go z npm za pomocą następującego polecenia:

$ npm install typescript@rc

W tej chwili musisz dodać rctag, aby zainstalować najnowszą wersję 3.8 maszynopisu.

Ahmed Bouchefra
źródło
Ale musisz wtedy wyjaśnić, jak go używać?
raarts
-2

Ponieważ main()działa asynchronicznie, zwraca obietnicę. Wynik musisz uzyskać then()metodą. A ponieważ then()zwroty też obiecują, musisz zadzwonić, process.exit()aby zakończyć program.

main()
   .then(
      (text) => { console.log('outside: ' + text) },
      (err)  => { console.log(err) }
   )
   .then(() => { process.exit() } )
Peracek
źródło
2
Źle. Gdy wszystkie obietnice zostaną zaakceptowane lub odrzucone, a kod nie jest już uruchomiony w głównym wątku, proces kończy się samoczynnie.
@Dev: normalnie chciałbyś przekazać różne wartości, exit()aby zasygnalizować, czy wystąpił błąd.
9000
@ 9000 Tak, ale nie jest to robione tutaj, a ponieważ kod zakończenia 0 jest domyślny, nie ma potrzeby go
@ 9000 w rzeczywistości program obsługi błędów powinien prawdopodobnie używaćprocess.exit(1)