Voorwaardelijke asynchrone afhankelijkheden

Webfuncties: Top-level await

Met await op het hoogste niveau kunnen modules fungeren als asynchrone functies. Dit betekent dat ze de uitvoering van modules kunnen onderbreken om te wachten op promises. Dit is uiterst nuttig om asynchrone afhankelijkheden, zoals polyfills of zware secundaire bibliotheken, alleen voorwaardelijk te laden als de browser dit vereist.

Door await op het hoogste niveau te gebruiken, kun je de voorwaardelijke laadlogica inkapselen in één module. Zo voorkom je dat downstream consumentenmodules worden uitgevoerd totdat de afhankelijkheid volledig is geladen en klaar is.

Voorwaardelijk polyfill-patroon

Hoewel await op het hoogste niveau kan worden gebruikt om voorwaardelijk een asynchrone afhankelijkheid te laden die een module is, is een goed gebruik van het patroon voor voorwaardelijk laden van afhankelijkheden om voorwaardelijk polyfills te laden voor browsers die een specifieke functie niet ondersteunen. Deze aanpak omvat functiedetectie en de dynamische import in één afhankelijkheidsmodule.

In het volgende geval wordt de polyfill van het kenmerk popover voorwaardelijk geladen als deze niet aanwezig is op HTMLElement.prototype:

// conditionally-load-polyfill.js

// Check if the feature is missing before doing work.
// MANDATORY: Prefer checking HTMLElement.prototype over window or document
// when checking for a global DOM attribute or property like popover.
if (!('popover' in HTMLElement.prototype)) {
  // Use top-level await to pause the execution of any module that imports this file 
  // until the polyfill finishes downloading and executing.
  await import('/path/to/popover-polyfill.js');
}

// Export a marker if needed by your application
export const polyfillLoaded = true;
// main.js

// MANDATORY: Because conditionally-load-polyfill.js uses top-level await, 
// this import will block execution of main.js until the polyfill is ready.
import './conditionally-load-polyfill.js';

// Now it is safe to use the feature (e.g., showing a popover)
const myPopover = document.getElementById('my-popover');
if (myPopover) {
  myPopover.showPopover();
}

Sequencing module imports and avoiding the pre-Safari 27 bug

VERPLICHT: Structureer je modulediagram zo dat de module await op het hoogste niveau wordt geïmporteerd vanuit één toegangspunt en wordt voltooid voordat downstream modules worden geëvalueerd:

  • Plaats GEEN statische imports van hetzelfde niveau in een invoerbestand (bijvoorbeeld import './conditionally-load-polyfill.js'; import './app.js';). Volgens de specificatie voor de evaluatie van ECMAScript-modules schort await op het hoogste niveau alleen de bovenliggende module op die deze importeert, niet de statische imports van hetzelfde niveau. ./app.js wordt dus gelijktijdig geëvalueerd in alle browsers voordat de polyfill klaar is met laden.
  • Importeer de module await op het hoogste niveau NIET vanuit meerdere sibling-modules tegelijk (bijvoorbeeld door zowel a.js als b.js statisch ./conditionally-load-polyfill.js te importeren). Hoewel Safari 27 de modulelader van WebKit heeft gecorrigeerd om gelijktijdige imports van await-modules op het hoogste niveau per specificatie te ondersteunen, mislukt Safari 15-26.6 als meerdere modules tegelijk een module importeren die een await op het hoogste niveau bevat (zie MDN-browsercompatibiliteit).

Kies in plaats daarvan een van de 2 patronen, afhankelijk van je modulestructuur:

  1. Module met één invoer (hierboven getoond in main.js): Als je invoermodule de afhankelijke initialisatielogica rechtstreeks uitvoert, importeer je ./conditionally-load-polyfill.js statisch bovenaan die module en voer je je code uit in de hoofdtekst.
  2. Afzonderlijke app-modules bootstrappen: Als je invoermodule delegeert naar downstream app-modules (zoals ./app.js), koppel je dynamische import()-aanroepen aan await op het hoogste niveau, zodat ./app.js alleen wordt opgehaald en geëvalueerd nadat de polyfill-module is opgelost:
// main.js

// MANDATORY: Chain dynamic imports with top-level await when bootstrapping separate
// application modules so './app.js' waits for the polyfill to finish loading.
// DO NOT use sibling static imports (`import './conditionally-load-polyfill.js'; import './app.js';`),
// which evaluate concurrently and execute './app.js' before the polyfill is ready.
await import('./conditionally-load-polyfill.js');
await import('./app.js');

Reservestrategieën

Hoewel de basisversie van await op het hoogste niveau sinds 2021 wordt ondersteund in de belangrijkste browsers (Chrome 89, Edge 89, Firefox 89 en Safari 15), voldoet deze pas sinds Safari 27 volledig aan de specificaties (zie MDN-browsercompatibiliteit).

  • Safari 15-26.6: Zolang je het patroon met één toegangspunt in het vorige gedeelte volgt, zodat meerdere sibling-modules niet tegelijkertijd de module await op het hoogste niveau importeren, kun je veilig vertrouwen op await op het hoogste niveau zonder extra fallback.
  • Verouderde browsers (van vóór 2021): Als je app browsers zonder ondersteuning voor await op het hoogste niveau moet ondersteunen, vermijd je await op het hoogste niveau en verpak je je voorwaardelijke import()-orkestratie in een standaard async-functie of .then()-promise-keten.