Wie kann ich async / await auf der obersten Ebene verwenden?

181

Ich habe gehen über wurde async/ awaitund nach über mehrere Artikel gehen, entschied ich mich zu Test Dinge selbst. Ich kann mich jedoch nicht darum kümmern, warum dies nicht funktioniert:

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

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

Die Konsole gibt Folgendes aus (Knoten v8.6.0):

> draußen: [Objekt Versprechen]

> drinnen: Hey da

Warum wird die Protokollnachricht in der Funktion anschließend ausgeführt? Ich dachte, der Grund async/ awaitwurde erstellt, um eine synchrone Ausführung mit asynchronen Aufgaben durchzuführen.

Gibt es eine Möglichkeit, den in der Funktion zurückgegebenen Wert zu verwenden, ohne ein .then()Nachher zu verwenden main()?

Felipe
quelle
4
Nein, nur Zeitmaschinen können asynchronen Code synchronisieren. awaitist nichts als Zucker für die Versprechenssyntax then.
Bergi
Warum gibt main einen Wert zurück? Wenn dies der Fall sein sollte, ist es wahrscheinlich kein Einstiegspunkt und muss von einer anderen Funktion (z. B. async IIFE) aufgerufen werden.
Estus Flask
@estus es war nur ein kurzer Funktionsname, während ich Dinge im Knoten testete, nicht unbedingt repräsentativ für ein Programmmain
Felipe
2
async/awaitZu Ihrer Information , ist Teil von ES2017, nicht von ES7 (ES2016)
Felix Kling

Antworten:

266

Ich kann mich nicht darum kümmern, warum das nicht funktioniert.

Weil mainein Versprechen zurückkommt; Alle asyncFunktionen funktionieren.

Auf der obersten Ebene müssen Sie entweder:

  1. Verwenden Sie eine asyncFunktion der obersten Ebene , die niemals ablehnt (es sei denn, Sie möchten "nicht behandelte Ablehnungsfehler") oder

  2. Verwenden Sie thenund catch, oder

  3. (In Kürze verfügbar!) Verwenden Sie Top-Levelawait , einen Vorschlag, der Stufe 3 des Prozesses erreicht hat und die Verwendung von Top-Level awaitin einem Modul ermöglicht.

# 1 - Top-Level- asyncFunktion, die niemals ablehnt

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

Beachten Sie die catch; Sie müssen mit Versprechungen / asynchronen Ausnahmen umgehen, da sonst nichts passiert. Sie haben keinen Anrufer, an den Sie sie weiterleiten können. Wenn Sie es vorziehen, können Sie dies anhand des Ergebnisses tun, indem Sie es über die catchFunktion (anstelle von try/ catchsyntax) aufrufen :

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

... was etwas prägnanter ist (ich mag es aus diesem Grund).

Oder behandeln Sie natürlich keine Fehler und lassen Sie einfach den Fehler "Nicht behandelte Ablehnung" zu.

# 2 - thenundcatch

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

Der catchHandler wird aufgerufen, wenn Fehler in der Kette oder in Ihrem thenHandler auftreten. (Stellen Sie sicher, dass Ihr catchHandler keine Fehler auslöst, da nichts registriert ist, um diese zu behandeln.)

Oder beide Argumente an then:

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

Beachten Sie erneut, dass wir einen Ablehnungshandler registrieren. Stellen Sie in dieser Form jedoch sicher, dass keiner Ihrer thenRückrufe Fehler verursacht und nichts registriert ist, um diese zu behandeln.

# 3 Top-Level awaitin einem Modul

Sie können awaitein Skript, das kein Modul ist, nicht auf der obersten Ebene verwenden. Mit dem awaitVorschlag auf oberster Ebene ( Stufe 3 ) können Sie es jedoch auf der obersten Ebene eines Moduls verwenden. Es ähnelt der Verwendung eines asyncFunktions-Wrappers der obersten Ebene (Nr. 1 oben), da Sie nicht möchten, dass Ihr Code der obersten Ebene zurückgewiesen wird (einen Fehler auslöst), da dies zu einem nicht behandelten Ablehnungsfehler führt. Wenn Sie also nicht diese unbehandelte Ablehnung haben möchten, wenn etwas schief geht, wie bei Nummer 1, möchten Sie Ihren Code in einen Fehlerbehandler einschließen:

// 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
}

Beachten Sie, dass in diesem Fall jedes Modul, das von Ihrem Modul importiert wird, wartet, bis das von Ihnen awaiteingegangene Versprechen erfüllt ist. Wenn ein Modul mit Top-Level awaitbewertet wird, gibt es im Grunde genommen ein Versprechen an den Modullader zurück (wie es eine asyncFunktion tut), der wartet, bis dieses Versprechen erfüllt ist, bevor er die Körper aller Module bewertet, die davon abhängen.

TJ Crowder
quelle
Wenn Sie es als Versprechen betrachten, erklären Sie jetzt, warum die Funktion sofort zurückkehrt. Ich habe mit der Erstellung einer anonymen asynchronen Funktion der obersten Ebene experimentiert und erhalte jetzt sinnvolle Ergebnisse
Felipe,
2
@Felipe: Ja, async/ awaitist syntaktischer Zucker um Versprechen (die gute Art von Zucker :-)). Sie denken nicht nur daran , ein Versprechen zurückzugeben. es tut es tatsächlich. ( Details .)
TJ Crowder
1
@LukeMcGregor - Ich habe beide oben gezeigt, mit der asyncOption all zuerst. Für die Funktion der obersten Ebene kann ich sie so oder so sehen (hauptsächlich aufgrund von zwei Einrückungsstufen in der asyncVersion).
TJ Crowder
3
@Felipe - Ich habe die Antwort jetzt aktualisiert, da der awaitVorschlag der obersten Ebene Stufe 3 erreicht hat :-)
TJ Crowder
1
@ SurajShrestha - Nein. Aber es ist kein Problem, das es nicht tut. :-)
TJ Crowder
7

Top-Levelawait ist zu Stufe 3 übergegangen , daher die Antwort auf Ihre Frage. Wie kann ich Async / Warten auf der obersten Ebene verwenden? ist einfach awaitden Anruf hinzuzufügen zu 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)

Oder nur:

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

Denken Sie daran, dass es immer noch nur unter [email protected] verfügbar ist .

Wenn Sie TypeScript verwenden , ist es in 3.8 gelandet .

v8 hat Unterstützung in Modulen hinzugefügt .

Es wird auch von Deno unterstützt (wie von Gonzalo-Bahamondez kommentiert).

Taro
quelle
Ziemlich cool. Haben wir eine Roadmap für eine Node-Implementierung
Felipe
Ich weiß es nicht, aber es ist sehr wahrscheinlich, dass wir bald eine TypeScript- und Babel-Implementierung sehen werden. Das TypeScript-Team hat eine Richtlinie zur Implementierung von Sprachfunktionen der Stufe 3, und ein Babel-Plugin wird normalerweise als Teil des TC39-Prozesses zum Testen von Vorschlägen erstellt. Siehe github.com/Microsoft/TypeScript/issues/…
Taro
Es ist auch in deno verfügbar (nur js, Typoskript unterstützt es immer noch nicht github.com/microsoft/TypeScript/issues/25988 ) deno.land siehe deno.news/issues/… .
Gonzalo Bahamondez
SyntaxError: Warten ist nur in der asynchronen Funktion gültig
Sudipta Dhara
4

Die eigentliche Lösung für dieses Problem besteht darin, es anders anzugehen.

Wahrscheinlich ist Ihr Ziel eine Art Initialisierung, die normalerweise auf der obersten Ebene einer Anwendung stattfindet.

Die Lösung besteht darin, sicherzustellen, dass sich auf der obersten Ebene Ihrer Anwendung immer nur eine einzige JavaScript-Anweisung befindet. Wenn Sie nur eine Anweisung oben in Ihrer Anwendung haben, können Sie async / await an jedem anderen Punkt überall verwenden (natürlich vorbehaltlich normaler Syntaxregeln).

Anders ausgedrückt: Wickeln Sie Ihre gesamte oberste Ebene in eine Funktion ein, sodass sie nicht mehr die oberste Ebene ist und die Frage löst, wie Async / Wait auf der obersten Ebene einer Anwendung ausgeführt werden soll - das tun Sie nicht.

So sollte die oberste Ebene Ihrer Anwendung aussehen:

import {application} from './server'

application();
Herzog Dougal
quelle
1
Sie haben Recht, dass mein Ziel die Initialisierung ist. Dinge wie Datenbankverbindungen, Datenabrufe usw. In einigen Fällen war es erforderlich, die Daten eines Benutzers abzurufen, bevor mit dem Rest der Anwendung fortgefahren werden konnte. Im Wesentlichen schlagen Sie vor, application()dass asynchron sein?
Felipe
1
Nein, ich sage nur, wenn nur eine JavaScript-Anweisung im Stammverzeichnis Ihrer Anwendung vorhanden ist, ist Ihr Problem behoben - die gezeigte Anweisung der obersten Ebene ist nicht asynchron. Das Problem ist, dass es nicht möglich ist, Async auf der obersten Ebene zu verwenden - Sie können nicht darauf warten, auf dieser Ebene tatsächlich zu warten. Wenn es also nur eine Anweisung auf der obersten Ebene gibt, haben Sie dieses Problem umgangen. Ihr asynchroner Initialisierungscode ist jetzt in einem importierten Code nicht mehr vorhanden. Daher funktioniert asynchroner Code einwandfrei, und Sie können alles zu Beginn Ihrer Anwendung initialisieren.
Duke Dougal
1
KORREKTUR - Anwendung ist eine asynchrone Funktion.
Duke Dougal
4
Es tut mir nicht klar, tut mir leid. Der Punkt ist, dass normalerweise auf der obersten Ebene keine asynchrone Funktion wartet ... JavaScript fährt direkt mit der nächsten Anweisung fort, sodass Sie nicht sicher sein können, ob Ihr Init-Code vollständig ist. Wenn oben in Ihrer Anwendung nur eine einzige Aussage steht, spielt das keine Rolle.
Duke Dougal
3

Um weitere Informationen zu den aktuellen Antworten zu geben:

Der Inhalt einer node.jsDatei wird derzeit stringartig verkettet, um einen Funktionskörper zu bilden.

Zum Beispiel, wenn Sie eine Datei haben test.js:

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

Dann node.jswird heimlich eine Funktion verkettet, die aussieht wie:

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

Das Wichtigste ist, dass die resultierende Funktion KEINE asynchrone Funktion ist. Sie können den Begriff also nicht awaitdirekt darin verwenden!

Angenommen, Sie müssen mit Versprechungen in dieser Datei arbeiten, dann gibt es zwei mögliche Methoden:

  1. Nicht await direkt in der Funktion verwenden
  2. Nicht benutzen await

Option 1 erfordert, dass wir einen neuen Bereich erstellen (und DIESER Bereich kann es sein async, weil wir die Kontrolle darüber haben):

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

Option 2 erfordert die Verwendung der objektorientierten Versprechen-API (das weniger hübsche, aber ebenso funktionale Paradigma der Arbeit mit Versprechen).

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

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

Ich persönlich hoffe, dass node.js, wenn es funktioniert, standardmäßig Code zu einer asyncFunktion verkettet . Das würde diese Kopfschmerzen loswerden.

Gershom
quelle
0

Das Warten auf höchster Ebene ist eine Funktion des kommenden EcmaScript-Standards. Derzeit können Sie es mit TypeScript 3.8 verwenden (derzeit in der RC-Version).

So installieren Sie TypeScript 3.8

Sie können TypeScript 3.8 starten, indem Sie es mit dem folgenden Befehl von npm aus installieren :

$ npm install typescript@rc

Zu diesem Zeitpunkt müssen Sie das rcTag hinzufügen , um die neueste TypScript 3.8-Version zu installieren.

Ahmed Bouchefra
quelle
Aber müssen Sie dann erklären, wie man es benutzt?
Raarts
-2

Da es main()asynchron läuft, gibt es ein Versprechen zurück. Sie müssen das Ergebnis in then()Methode erhalten. Und weil auch die then()Rückgabe vielversprechend ist, müssen Sie anrufen process.exit(), um das Programm zu beenden.

main()
   .then(
      (text) => { console.log('outside: ' + text) },
      (err)  => { console.log(err) }
   )
   .then(() => { process.exit() } )
Peracek
quelle
2
Falsch. Sobald alle Versprechen angenommen oder abgelehnt wurden und kein Code mehr im Hauptthread ausgeführt wird, wird der Prozess von selbst beendet.
@Dev: Normalerweise möchten Sie verschiedene Werte übergeben, um exit()zu signalisieren, ob ein Fehler aufgetreten ist.
9000
@ 9000 Ja, aber das wird hier nicht gemacht, und da ein Exit-Code von 0 die Standardeinstellung ist, muss er nicht
@ 9000 in der Tat sollte der Fehlerbehandler wahrscheinlich verwendenprocess.exit(1)