Używanie await poza funkcją async

86

Próbowałem połączyć ze sobą dwie funkcje asynchroniczne, ponieważ pierwsza miała warunkowy parametr zwrotny, który powodował, że druga albo uruchamiała, albo wychodziła z modułu. Jednak zauważyłem dziwne zachowanie, którego nie mogę znaleźć w specyfikacjach.

async function isInLobby() {
    //promise.all([chained methods here])
    let exit = false;
    if (someCondition) exit = true;
}

To jest zdegradowany fragment mojego kodu (możesz zobaczyć pełny zakres tutaj ), który po prostu sprawdza, czy gracz jest już w lobby, ale to nie ma znaczenia.

Następnie mamy tę funkcję asynchroniczną.

async function countPlayer() {
    const keyLength = await scardAsync(game);
    return keyLength;
}

Ta funkcja nie musi być uruchamiana, jeśli exit === true.

Próbowałem to zrobić

const inLobby = await isInLobby();

Miałem nadzieję, że będzie to czekało na wyniki, więc mogę użyć inLobbydo uruchomienia warunkowego countPlayer, jednak otrzymałem błąd typu bez konkretnych szczegółów.

Dlaczego nie możesz poza funkcją zakresu funkcji? Wiem, że to obietnica cukru, więc musi być przykuta, ale dlaczego w środku mogę czekać na kolejną obietnicę, ale na zewnątrz nie mogę ?awaitasyncthencountPlayerawait isInLobby

Sterling Archer
źródło
Czy możesz nam pokazać, gdzie robiłeś await isInLobby()i jak inLobbyjest używany? Poza tym, gdzie / jak się countPlayernazywa?
Bergi
@Bergi Połączyłem moje repozytorium, aby zobaczyć rzeczywisty kontekst. Za dużo kodu, by zadać pytanie
Sterling Archer
Nie widzę, gdzie jest z tym problem (może już zaktualizowałeś repozytorium)? Jeśli odwołujesz się do isInLobby().then( … countPlayer().then …części, rozwiązanie jest trywialne: po prostu stwórz funkcję, w której zawarte są te wywołania (tę (req, res) =>) async.
Bergi
@Bergi problem nie polega na tym, że został uszkodzony, działa tak, jak jest. Po prostu nie rozumiałem, dlaczego czekanie na najwyższym poziomie nie jest rzeczą. Okazuje się, że jeszcze nie istnieje bez określenia zakresu całego modułu jako funkcji asynchronicznej
Sterling Archer
Ale nie potrzebujesz nawet najwyższego poziomu await dla swojego kodu? Dlatego zastanawiałem się, że zaakceptowałeś odpowiedź, która tak naprawdę nie dotyczy problemu w pytaniu.
Bergi

Odpowiedzi:

73

Najwyższy poziom awaitnie jest obsługiwany. Komisja normalizacyjna prowadzi kilka dyskusji na temat przyczyn takiego stanu rzeczy , na przykład ten problem na Githubie .

Na Githubie jest też dyskusja na temat tego, dlaczego czekanie na najwyższy poziom to zły pomysł. W szczególności sugeruje, że jeśli masz taki kod:

// data.js
const data = await fetch( '/data.json' );
export default data;

Teraz każdy importowany plik data.jsnie zostanie wykonany przed zakończeniem pobierania, więc całe ładowanie modułu jest teraz zablokowane. To sprawia, że ​​bardzo trudno jest wnioskować o kolejności modułów aplikacji, ponieważ jesteśmy przyzwyczajeni do wykonywania skryptów JavaScript najwyższego poziomu w sposób synchroniczny i przewidywalny. Gdyby było to dozwolone, wiedza, kiedy funkcja zostanie zdefiniowana, staje się trudna.

Z mojego punktu widzenia jest to zła praktyka, aby moduł powodował efekty uboczne po prostu ładując go. Oznacza to, że każdy konsument twojego modułu odczuje skutki uboczne po prostu wymagając twojego modułu. To bardzo ogranicza możliwości użycia Twojego modułu. Najwyższy poziom awaitprawdopodobnie oznacza, że ​​czytasz z jakiegoś interfejsu API lub dzwonisz do jakiejś usługi w czasie ładowania. Zamiast tego należy po prostu wyeksportować funkcje asynchroniczne, z których konsumenci mogą korzystać we własnym tempie.

Andy Ray
źródło
To dobry link, dziękuję. Szkoda, że ​​nie ma wsparcia na najwyższym poziomie. Mam nadzieje ze jest. Obecnie w obecnej sytuacji muszę tu składać swoje obietnice, a to jest bardzo zła praktyka i nie podoba mi się to. :( Dziękuję.
Sterling Archer,
@SterlingArcher alternatywnie użyj async IIFE:void async function() { const inLobby = await isInLobby() }()
robertklep
@robertklep nie obejmowałby zakresu inLobbyfunkcji, która czyni ją niedostępną?
Sterling Archer
@SterlingArcher tak, wymagałoby to przeniesienia tam całego kodu (zasadniczo czyniąc go „najwyższym poziomem”). To tylko alternatywa dla używania .then()(przepraszam, powinienem był to trochę wyjaśnić).
robertklep
nie zgadzam się, użytkownik data.js jest „zablokowany”, niemniej jednak podczas ładowania samego pliku data.js i wszystkich jego zależności, pojęcie „blokowania” samo w sobie nie jest złe. najwyższy poziom await może być traktowany jako ładowanie pewnych zależności, które wydają się potrzebne, zanim użycie zostanie „zwolnione”.
Ibrahim ben Salah
125

Oczywiście jest to zawsze:

(async () => {
    await ...

    // all of the script.... 

})();
// nothing else

To sprawia, że ​​jest to szybka funkcja z async, w której możesz użyć await. Oszczędza to potrzeby tworzenia funkcji asynchronicznej, która jest świetna! // kredyty Silve2611

digerati-stratagies
źródło
3
Opisz dalej i wyjaśnij, dlaczego to działa.
Paul Back
12
Głosowanie za tą odpowiedzią jest bardzo głupie. Robi szybką funkcję z async, w której możesz użyć await. Oszczędza to potrzeby tworzenia funkcji asynchronicznej, która jest świetna!
Silve2611
55
Nie rozwiązuje problemu, ponieważ nadal potrzebujesz awaittej anonimowej funkcji, która znowu nie działa spoza funkcji.
Michael
więc jak to obsłuży warunek błędu? czy wyląduje również w kodzie await?
Manuel Hernandez
Tks to wspaniała pomoc, specjalnie dla obietnicy wywołania zwrotnego po pobraniu interfejsu API logowania, która jest odpowiedzią idealnie w celu uzyskania pamięci. GetItem
tess hsu
9

Jeszcze lepiej jest umieścić dodatkowy średnik przed blokiem kodu

;(async () => {
    await ...
})();

Zapobiega to automatycznemu formatowaniu (np. W vscode) przeniesieniu pierwszego nawiasu na koniec poprzedniej linii.

Problem można przedstawić na następującym przykładzie:

const add = x => y => x+y
const increment = add(1)
(async () => {
    await ...
})();

Bez średnika zostanie to ponownie sformatowane jako:

const add = x => y => x+y
const increment = add(1)(async () => {
  await Promise(1)
})()

co oczywiście jest błędne, ponieważ przypisuje funkcję asynchroniczną jako yparametr i próbuje wywołać funkcję z wyniku (który jest w rzeczywistości dziwnym ciągiem '1async () => {...}')

Viliam Simko
źródło
20
To nie reformatter jest zły. Bez średnika w ten sposób Twoja funkcja byłaby analizowana w środowisku wykonawczym i otrzymywałbyś błąd, podział wiersza lub brak podziału wiersza. Prawidłowym rozwiązaniem w tym przykładzie byłoby dodanie średnika po add(1);funkcji asynchronicznej, a nie przed nią.
Madara's Ghost
11
Poza tym, szczerze mówiąc, nie rozumiem, jak to się ma do omawianego pytania.
Madara's Ghost
Tak, masz rację. Środowisko wykonawcze również potrzebuje średnika.
Viliam Simko
1

możesz czekać na najwyższy poziom, ponieważ maszynopis 3.8
https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-8.html#-top-level-await
Z postu:
To dlatego, że wcześniej w JavaScript (wraz z większością innych języków o podobnej funkcji), await był dozwolony tylko w treści funkcji async. Jednak w przypadku najwyższego poziomu await możemy użyć await na najwyższym poziomie modułu.

const response = await fetch("...");
const greeting = await response.text();
console.log(greeting);

// Make sure we're a module
export {};

Zauważ, że jest pewna subtelność: najwyższy poziom await działa tylko na najwyższym poziomie modułu, a pliki są uznawane za moduły tylko wtedy, gdy TypeScript znajdzie import lub eksport. W niektórych podstawowych przypadkach może być konieczne napisanie eksportu {} jako szablonu, aby się tego upewnić.

Oczekiwanie najwyższego poziomu może nie działać we wszystkich środowiskach, w których można by się tego spodziewać. Obecnie można używać tylko najwyższego poziomu await, gdy opcja kompilatora docelowego to es2017 lub nowsza, a module to esnext lub system. Wsparcie w kilku środowiskach i pakietach może być ograniczone lub może wymagać włączenia obsługi eksperymentalnej.

TOPÓR
źródło