Dependencias asíncronas condicionales

Funciones web: Await de nivel superior

El await de nivel superior permite que los módulos actúen como funciones asíncronas, lo que significa que pueden pausar la ejecución del módulo para esperar promesas. Esto es muy útil para cargar de forma condicional dependencias asíncronas, como polyfills o bibliotecas secundarias pesadas, solo cuando el navegador lo requiera.

Si usas await de nivel superior, puedes encapsular la lógica de carga condicional dentro de un solo módulo, lo que evita que los módulos de consumidor descendentes se ejecuten hasta que la dependencia se cargue por completo y esté lista.

Patrón de polyfill condicional

Si bien se puede usar await de nivel superior para cargar de forma condicional cualquier dependencia asíncrona que sea un módulo, un buen uso del patrón de carga de dependencias condicional es cargar polyfills de forma condicional para los navegadores que no admiten una función específica. Este enfoque encapsula la detección de funciones y la importación dinámica dentro de un solo módulo de dependencia.

En el siguiente caso, el polyfill del atributo popover se carga de forma condicional si no está presente en 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();
}

Secuenciar las importaciones de módulos y evitar el error de Safari anterior a la versión 27

OBLIGATORIO: Debes estructurar el gráfico de módulos de modo que el módulo await de nivel superior se importe desde un único punto de entrada y se complete antes de que se evalúen los módulos descendentes:

  • NO coloques importaciones estáticas de hermanos en un archivo de entrada (por ejemplo, import './conditionally-load-polyfill.js'; import './app.js';). Según la especificación de evaluación del módulo de ECMAScript, el await de nivel superior solo suspende el módulo superior que lo importa, no las importaciones estáticas de hermanos, por lo que ./app.js se evaluará de forma simultánea en todos los navegadores antes de que termine de cargarse el polyfill.
  • NO importes el módulo await de nivel superior desde varios módulos hermanos de forma simultánea (por ejemplo, que a.js y b.js importen ./conditionally-load-polyfill.js de forma estática). Si bien Safari 27 corrigió el cargador de módulos de WebKit para admitir importaciones simultáneas de módulos await de nivel superior según las especificaciones, Safari 15-26.6 falla cuando varios módulos importan de forma simultánea un módulo que contiene un await de nivel superior (consulta la compatibilidad con navegadores de MDN).

En su lugar, elige uno de los dos patrones según la estructura de tu módulo:

  1. Módulo de entrada único (se muestra en main.js más arriba): Si tu módulo de entrada ejecuta la lógica de inicialización dependiente directamente, importa ./conditionally-load-polyfill.js de forma estática en la parte superior de ese módulo y ejecuta tu código en su cuerpo.
  2. Inicializa módulos de aplicación independientes: Si tu módulo de entrada delega en módulos de aplicación descendentes (como ./app.js), encadena llamadas dinámicas de import() con await de nivel superior para que ./app.js solo se recupere y evalúe después de que se resuelva el módulo de polyfill:
// 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');

Estrategias de resguardo

Si bien el await básico de nivel superior es compatible con los principales navegadores desde 2021 (Chrome 89, Edge 89, Firefox 89 y Safari 15), solo alcanzó el cumplimiento total de las especificaciones en Safari 27 (consulta la compatibilidad con navegadores de MDN).

  • Safari 15-26.6: Siempre que sigas el patrón de punto de entrada único de la sección anterior para que varios módulos hermanos no importen de forma simultánea el módulo await de nivel superior, puedes confiar de forma segura en el await de nivel superior sin un resguardo adicional.
  • Navegadores heredados (anteriores a 2021): Si tu aplicación debe admitir navegadores sin compatibilidad con await de nivel superior, evita await de nivel superior y une tu organización condicional de import() en una función async estándar o una cadena de promesas .then().