Top-level `await`
Top-level await
ermöglicht Entwicklern, das await
-Schlüsselwort außerhalb von asynchronen Funktionen zu verwenden. Es verhält sich wie eine große asynchrone Funktion, wodurch andere Module, die sie importieren
, darauf warten, bevor sie beginnen, ihren Körper zu evaluieren.
Das alte Verhalten
Als async
/await
erstmals eingeführt wurde, führte der Versuch, ein await
außerhalb einer async
-Funktion zu verwenden, zu einem SyntaxError
. Viele Entwickler verwendeten direkt aufgerufene asynchrone Funktionsausdrücke, um Zugriff auf die Funktionalität zu erhalten.
await Promise.resolve(console.log('🎉'));
// → SyntaxError: await ist nur gültig in einer asynchronen Funktion
(async function() {
await Promise.resolve(console.log('🎉'));
// → 🎉
}());
Das neue Verhalten
Mit top-level await
funktioniert der obige Code wie erwartet innerhalb von Modulen:
await Promise.resolve(console.log('🎉'));
// → 🎉
Hinweis: Top-Level-await
funktioniert nur auf der obersten Ebene von Modulen. Es gibt keine Unterstützung für klassische Skripte oder nicht asynchrone Funktionen.
Anwendungsfälle
Diese Anwendungsfälle stammen aus dem Spec Proposal Repository.
Dynamische Abhängigkeitspfadfindung
const strings = await import(`/i18n/${navigator.language}`);
Dies ermöglicht es Modulen, Laufzeitwerte zu verwenden, um Abhängigkeiten zu bestimmen. Dies ist nützlich für Dinge wie Entwicklungs-/Produktionsaufteilungen, Internationalisierung, Umgebungssplits usw.
Ressourceninitialisierung
const connection = await dbConnector();
Dies ermöglicht es Modulen, Ressourcen darzustellen und auch Fehler zu erzeugen, in Fällen, in denen das Modul nicht verwendet werden kann.
Abhängigkeits-Backups
Das folgende Beispiel versucht, eine JavaScript-Bibliothek von CDN A zu laden und wechselt zu CDN B, falls dies fehlschlägt:
let jQuery;
try {
jQuery = await import('https://cdn-a.example.com/jQuery');
} catch {
jQuery = await import('https://cdn-b.example.com/jQuery');
}
Modulausführungsreihenfolge
Eine der größten Veränderungen in JavaScript mit top-level await
ist die Reihenfolge der Ausführung von Modulen in Ihrem Graph. Die JavaScript-Engine führt Module in post-order traversal aus: Ausgehend vom ganz linken Teilbaum Ihres Modulgraphen werden Module ausgewertet, ihre Bindungen exportiert, und ihre Geschwister ausgeführt, gefolgt von ihren Eltern. Dieser Algorithmus läuft rekursiv, bis er die Wurzel Ihres Modulgraphen ausführt.
Vor top-level await
war diese Reihenfolge immer synchron und deterministisch: Zwischen mehreren Ausführungen Ihres Codes war Ihr Graph garantiert, in der gleichen Reihenfolge ausgeführt zu werden. Sobald top-level await
implementiert ist, bleibt diese Garantie bestehen, aber nur, solange Sie kein top-level await
verwenden.
Hier ist, was passiert, wenn Sie top-level await
in einem Modul verwenden:
- Die Ausführung des aktuellen Moduls wird verschoben, bis das erwartete Versprechen aufgelöst wird.
- Die Ausführung des übergeordneten Moduls wird verschoben, bis das untergeordnete Modul, das
await
aufgerufen hat, und alle seine Geschwister Bindungen exportieren. - Die Geschwistermodule und Geschwister der übergeordneten Module können weiterhin in derselben synchronen Reihenfolge ausgeführt werden — vorausgesetzt, es gibt keine Zyklen oder andere
await
-Promises im Graphen. - Das Modul, das
await
aufgerufen hat, setzt seine Ausführung fort, nachdem dasawait
-Promise aufgelöst wird. - Das übergeordnete Modul und nachfolgende Baumstrukturen werden in synchroner Reihenfolge ausgeführt, solange keine weiteren
await
-Promises vorhanden sind.
Funktioniert das nicht bereits in DevTools?
Ja, tatsächlich! Der REPL in Chrome DevTools, Node.js und Safari Web Inspector hat top-level await
schon seit einiger Zeit unterstützt. Diese Funktionalität war jedoch nicht standardmäßig und auf den REPL begrenzt! Sie unterscheidet sich vom Vorschlag für top-level await
, der Teil der Sprachspezifikation ist und nur für Module gilt. Um Produktionscode, der auf top-level await
basiert, so zu testen, dass er vollständig mit den Semantiken des Specs-Vorschlags übereinstimmt, stellen Sie sicher, dass Sie in Ihrer tatsächlichen App testen und nicht nur in DevTools oder im Node.js REPL!
Ist top-level await
nicht ein Problem?
Vielleicht haben Sie den berüchtigten Gist von Rich Harris gesehen, der ursprünglich eine Reihe von Bedenken bezüglich top-level await
skizzierte und die JavaScript-Sprache aufforderte, das Feature nicht zu implementieren. Einige spezifische Bedenken waren:
- Top-level
await
könnte die Ausführung blockieren. - Top-level
await
könnte das Abrufen von Ressourcen blockieren. - Es gäbe keine klare Interoperabilitätslösung für CommonJS-Module.
Die Version des Vorschlags in Phase 3 adressiert diese Themen direkt:
- Da Geschwistermodule gleichzeitig ausgeführt werden können, gibt es keine definitive Blockierung.
- Top-level
await
findet während der Ausführungsphase des Modulgraphen statt. Zu diesem Zeitpunkt wurden alle Ressourcen bereits abgerufen und verknüpft. Es besteht kein Risiko, dass das Abrufen von Ressourcen blockiert wird. - Top-level
await
ist auf Module beschränkt. Es gibt ausdrücklich keine Unterstützung für Skripte oder CommonJS-Module.
Wie bei jeder neuen Sprachfunktion gibt es immer ein Risiko für unerwartetes Verhalten. Zum Beispiel könnten zirkuläre Modulabhängigkeiten bei top-level await
zu einer Sackgasse führen.
Ohne top-level await
verwenden JavaScript-Entwickler oft asynchrone sofort aufgerufene Funktionsausdrücke, nur um Zugriff auf await
zu erhalten. Leider führt dieses Muster zu weniger Determinismus in der Ausführung des Modulgraphen und zur geringeren statischen Analysierbarkeit von Anwendungen. Aus diesen Gründen wurde das Fehlen von top-level await
als ein höheres Risiko angesehen als die mit der Funktion eingeführten Gefahren.