Verwenden Sie "global" in JavaScript





Eine neue Funktion, die die Art



und Weise, wie wir JavaScript schreiben, verändern könnte, ist eine hochflexible und leistungsstarke Sprache, die die Entwicklung des modernen Web prägt. Einer der Hauptgründe, warum JavaScript in der Webentwicklung so dominant ist, ist seine schnelle Entwicklung und kontinuierliche Verbesserung.



Ein Vorschlag zur Verbesserung von JavaScript ist der Vorschlaggenannt "Top-Level warten" (Top-Level warten, "global" warten). Ziel dieses Vorschlags ist es, ES-Module in so etwas wie asynchrone Funktionen umzuwandeln. Auf diese Weise können Module einsatzbereite Ressourcen abrufen und den Import von Modulen blockieren. Module, die erwartete Ressourcen importieren, können die Codeausführung erst ausführen, nachdem die Ressourcen empfangen und für die Verwendung vorbereitet wurden.



Dieser Vorschlag befindet sich derzeit in drei Phasen der Prüfung, sodass diese Funktion noch nicht in der Produktion verwendet werden kann. Sie können jedoch sicher sein, dass es in naher Zukunft sicherlich implementiert wird.



Mach dir darüber keine Sorgen. Weiter lesen. Ich werde Ihnen zeigen, wie Sie die genannte Funktion jetzt verwenden können.



Was ist los mit normalem Warten?



Wenn Sie versuchen, das Schlüsselwort "await" außerhalb einer asynchronen Funktion zu verwenden, wird ein Syntaxfehler angezeigt. Um dies zu vermeiden, verwenden Entwickler den sofort aufgerufenen Funktionsausdruck (IIFE).



await Promise.resolve(console.log("️")); // 

(async () => {
    await Promise.resolve(console.log("️"))
})();


Das angegebene Problem und seine Lösung sind nur die Spitze des Eisbergs.




Wenn Sie mit ES6-Modulen arbeiten, beschäftigen Sie sich häufig mit vielen Instanzen, die Werte exportieren und importieren. Betrachten wir ein Beispiel:



// library.js
export const sqrt = Math.sqrt;
export const square = (x) => x * x;
export const diagonal = (x, y) => sqrt((square(x) + square(y)));

// middleware.js
import { square, diagonal } from "./library.js";

console.log("From Middleware");

let squareOutput;
let diagonalOutput;

const delay = (ms) => new Promise((resolve) => {
    const timer = setTimeout(() => {
        resolve(console.log("️"));
        clearTimeout(timer);
    }, ms);
});

// IIFE
(async () => {
    await delay(1000);
    squareOutput = square(13);
    diagonalOutput = diagonal(12, 5);
})();

export { squareOutput, diagonalOutput };


Im obigen Beispiel exportieren und importieren wir Variablen zwischen library.js und middleware.js. Sie können die Dateien beliebig benennen.



Die Verzögerungsfunktion gibt ein Versprechen zurück, das nach einer Verzögerung aufgelöst wird. Da diese Funktion asynchron ist, verwenden wir das Schlüsselwort "await" im IIFE, um auf den Abschluss zu warten. In einer realen Anwendung wird anstelle der Funktion "Verzögerung" ein Abruf oder eine andere asynchrone Aufgabe ausgeführt. Nach der Lösung des Versprechens weisen wir unserer Variablen den Wert zu. Dies bedeutet, dass unsere Variable undefiniert bleibt, bis das Versprechen gelöst ist.



Am Ende des Codes exportieren wir unsere Variablen, damit sie in anderem Code verwendet werden können.



Werfen wir einen Blick auf den Code, in den diese Variablen importiert und verwendet werden:



// main.js
import { squareOutput, diagonalOutput } from "./middleware.js";

console.log(squareOutput); // undefined
console.log(diagonalOutput); // undefined
console.log("From Main");

const timer1 = setTimeout(() => {
    console.log(squareOutput);
    clearTimeout(timer1);
}, 2000); // 169

const timer2 = setTimeout(() => {
    console.log(diagonalOutput);
    clearTimeout(timer2);
}, 2000); // 13


Wenn Sie diesen Code ausführen, werden Sie in den ersten beiden Fällen undefiniert und in den dritten und vierten Fällen 169 und 13. Warum passiert es?



Dies liegt an der Tatsache, dass wir versuchen, die Werte von Variablen, die aus middleware.js in main.js exportiert wurden, vor der Ausführung der asynchronen Funktion abzurufen. Erinnern Sie sich, dass wir dort ein Versprechen haben, das noch aussteht?



Um dieses Problem zu lösen, müssen wir das Importmodul irgendwie darüber informieren, dass die Variablen einsatzbereit sind.



Problemumgehungen


Es gibt mindestens zwei Möglichkeiten, um dieses Problem zu lösen.



1. Exportieren eines Versprechens zur Initialisierung


Erstens kann das IIFE exportiert werden. Das Schlüsselwort async macht die Methode asynchron. Eine solche Methode gibt immer ein Versprechen zurück. Aus diesem Grund gibt das asynchrone IIFE im folgenden Beispiel ein Versprechen zurück.



// middleware.js
import { square, diagonal } from "./library.js";

console.log("From Middleware");

let squareOutput;
let diagonalOutput;

const delay = (ms) => new Promise((resolve) => {
    const timer = setTimeout(() => {
        resolve(console.log("️"));
        clearTimeout(timer);
    }, ms);
});

//   ,   , 
export default (async () => {
    await delay(1000);
    squareOutput = square(13);
    diagonalOutput = diagonal(12, 5);
})();

export { squareOutput, diagonalOutput };


Während Sie auf die exportierten Variablen in main.js zugreifen, können Sie auf die Ausführung des IIFE warten.



// main.js
import promise, { squareOutput, diagonalOutput } from "./middleware.js";

promise.then(() => {
    console.log(squareOutput); // 169
    console.log(diagonalOutput); // 169
    console.log("From Main");
});

const timer1 = setTimeout(() => {
    console.log(squareOutput);
    clearTimeout(timer1);
}, 2000); // 169

const timer2 = setTimeout(() => {
    console.log(diagonalOutput);
    clearTimeout(timer2);
}, 2000); // 13


Trotz der Tatsache, dass dieses Snippet das Problem löst, führt es zu anderen Problemen.



  • Wenn Sie die angegebene Vorlage verwenden, müssen Sie nach dem gewünschten Versprechen suchen
  • Wenn ein anderes Modul auch die Variablen "squareOutput" und "diagonalOutput" verwendet, müssen wir sicherstellen, dass das IIFE erneut exportiert wird


Es gibt auch einen anderen Weg.



2. Auflösung des IIFE-Versprechens mit exportierten Variablen


In diesem Fall werden die Variablen nicht einzeln exportiert, sondern von unserem asynchronen IIFE zurückgegeben. Dadurch kann die Datei "main.js" einfach darauf warten, dass das Versprechen aufgelöst und sein Wert abgerufen wird.



// middleware.js
import { square, diagonal } from "./library.js";

console.log("From Middleware");

let squareOutput;
let diagonalOutput;

const delay = (ms) => new Promise((resolve) => {
    const timer = setTimeout(() => {
        resolve(console.log("️"));
        clearTimeout(timer);
    }, ms);
});

//  
export default (async () => {
    await delay(1000);
    squareOutput = square(13);
    diagonalOutput = diagonal(12, 5);
    return { squareOutput, diagonalOutput };
})();

// main.js
import promise from "./middleware.js";

promise.then(({ squareOutput, diagonalOutput }) => {
    console.log(squareOutput); // 169
    console.log(diagonalOutput); // 169
    console.log("From Main");
});

const timer1 = setTimeout(() => {
    console.log(squareOutput);
    clearTimeout(timer1);
}, 2000); // 169

const timer2 = setTimeout(() => {
    console.log(diagonalOutput);
    clearTimeout(timer2);
}, 2000); // 13


Diese Lösung hat jedoch auch einige Nachteile.



Dem Vorschlag zufolge hat „dieses Muster einen schwerwiegenden Nachteil, da es eine wesentliche Umgestaltung der zugehörigen Ressourcenquelle in dynamischere Vorlagen erfordert und den größten Teil des Modulkörpers in den Rückruf .then () einfügt, um dynamische Module zu ermöglichen. Dies stellt eine signifikante Regression in Bezug auf statische Analysefähigkeit, Testbarkeit, Ergonomie und mehr im Vergleich zu ES2015-Modulen dar. "



Wie löst "global" dieses Problem?



Das Warten auf höchster Ebene ermöglicht es einem modularen System, sich um die Lösung von Versprechungen und deren Interaktion zu kümmern.



// middleware.js
import { square, diagonal } from "./library.js";

console.log("From Middleware");

let squareOutput;
let diagonalOutput;

const delay = (ms) => new Promise((resolve) => {
    const timer = setTimeout(() => {
        resolve(console.log("️"));
        clearTimeout(timer);
    }, ms);
});

// "" await
await delay(1000);
squareOutput = square(13);
diagonalOutput = diagonal(12, 5);

export { squareOutput, diagonalOutput };

// main.js
import { squareOutput, diagonalOutput } from "./middleware.js";

console.log(squareOutput); // 169
console.log(diagonalOutput); // 13
console.log("From Main");

const timer1 = setTimeout(() => {
    console.log(squareOutput);
    clearTimeout(timer1);
}, 2000); // 169

const timer2 = setTimeout(() => {
    console.log(diagonalOutput);
    clearTimeout(timer2);
}, 2000); // 13


Keine der Anweisungen in main.js wird ausgeführt, bis Versprechen in middleware.js aufgelöst werden. Dies ist eine viel sauberere Lösung als die Problemumgehungen.



Die Notiz


Das globale Warten funktioniert nur mit ES-Modulen. Die verwendeten Abhängigkeiten müssen explizit angegeben werden. Das folgende Beispiel aus dem Angebots-Repository zeigt dies gut.



// x.mjs
console.log("X1");
await new Promise(r => setTimeout(r, 1000));
console.log("X2");
// y.mjs
console.log("Y");
// z.mjs
import "./x.mjs";
import "./y.mjs";
// X1
// Y
// X2


Dieses Snippet zeigt der Konsole nicht wie erwartet X1, X2, Y an, da x und y separate Module sind, die nicht miteinander verbunden sind.



Ich empfehle Ihnen dringend, den FAQ-Abschnitt des Vorschlags zu lesen, um die betreffende Funktion besser zu verstehen.



Implementierung



V8


Sie können diese Funktion jetzt testen.



Wechseln Sie dazu in das Verzeichnis, in dem sich Chrome auf Ihrem Computer befindet. Stellen Sie sicher, dass alle Browser-Registerkarten geschlossen sind. Öffnen Sie ein Terminal und geben Sie den folgenden Befehl ein:



chrome.exe --js-flags="--harmony-top-level-await"


Sie können diese Funktion auch in Node.js ausprobieren. Lesen Sie diese Anleitung , um mehr zu erfahren.



ES-Module


Stellen Sie sicher, dass Sie dem Tag "script" das Attribut "type" mit dem Wert "module" hinzufügen.



<script type="module" src="./index.js"></script>


Bitte beachten Sie, dass ES6-Module im Gegensatz zu normalen Skripten einer SOP-Richtlinie (Shared Origin (Single Source)) und einer CORS-Richtlinie (Resource Sharing) folgen. Daher ist es besser, mit ihnen auf dem Server zu arbeiten.



Anwendungsfälle



Dem Vorschlag zufolge warten die Anwendungsfälle für "global" wie folgt:



Dynamischer Abhängigkeitspfad


const strings = await import(`/i18n/${navigator.language}`);


Dies ermöglicht es Modulen, Laufzeitwerte zum Berechnen von Abhängigkeitspfaden zu verwenden, und kann nützlich sein, um Entwicklungs- / Produktionscode, Internationalisierung, Aufteilen von Code in Abhängigkeit von der Laufzeit (Browser, Node.js) usw. zu entkoppeln.



Ressourcen initialisieren


const connection = await dbConnector()


Dies hilft Modulen, einsatzbereite Ressourcen zu erhalten und Ausnahmen auszulösen, wenn das Modul nicht verwendet werden kann. Dieser Ansatz kann wie unten gezeigt als Sicherheitsnetz verwendet werden.



Fallback-Option


Das folgende Beispiel zeigt, wie ein "globales" Warten verwendet werden kann, um eine Abhängigkeit mit einer Fallback-Implementierung zu laden. Wenn der Import von CDN A fehlschlägt, wird der Import von CDN B ausgeführt:



let jQuery;
try {
  jQuery = await import('https://cdn-a.example.com/jQuery');
} catch {
  jQuery = await import('https://cdn-b.example.com/jQuery');
}


Kritik



Rich Harris hat eine Liste mit Kritikpunkten auf höchster Ebene zusammengestellt. Es enthält Folgendes:



  • "Global" warten kann die Codeausführung blockieren
  • "Globales" Warten kann die Ressourcenbeschaffung blockieren
  • Fehlende Unterstützung für CommonJS-Module


Antworten auf diese Kommentare finden Sie im FAQ-Vorschlag:



  • Da untergeordnete Knoten (Module) ausgeführt werden können, erfolgt letztendlich keine Codeblockierung
  • Das "globale" Warten wird während der Ausführungsphase eines Modulgraphen verwendet. Zu diesem Zeitpunkt werden alle Ressourcen empfangen und verknüpft, sodass kein Risiko besteht, die Ressourcenbeschaffung zu blockieren
  • Das Warten auf oberster Ebene ist auf ES6-Module beschränkt. Die Unterstützung von CommonJS-Modulen wie regulären Skripten war ursprünglich nicht geplant


Auch hier empfehle ich dringend, die FAQ des Vorschlags zu lesen .



Ich hoffe, ich konnte das Wesentliche des fraglichen Vorschlags auf zugängliche Weise erläutern. Wirst du diese Gelegenheit nutzen? Teilen Sie Ihre Meinung in den Kommentaren.



All Articles