Trust is earned, not given

A different perspective

2020-11-10 · Projects

Node.js, part 12: top-level await — when a module is its own init sequence

Part 12from the Node.js series · 24 parts in all

Before top-level await (Node 14.8), a module that needed data before it could export anything had exactly one shape: export a promise, and make every importer remember to await it. The pattern leaked into every consumer and every consumer could forget. Top-level await lets the module own its own readiness.

The difference, side by side

// Before: the module exports a promise; every caller must unwrap it,
// and every caller can forget.
export const configPromise = loadConfig();

// Afterwards: the module is not "ready" until this resolves.
import { loadConfig } from './config.js';

const config = await loadConfig();     // only legal in an ES module
export default config;

Importers see a module that has already initialised. That is genuinely simpler — and it moves the waiting somewhere you cannot see, which is the trade.

The rules that come with it

// Two round trips.
const users = await loadUsers();
const orders = await loadOrders();

// One round trip.
const [users, orders] = await Promise.all([loadUsers(), loadOrders()]);

When to reach for it

Genuinely once-per-process setup: read a config file, open a database pool, resolve a secret. Not for request-time data. And there is an operational hazard worth designing around: because node's module graph is a graph, the slowest top-level await gates the whole application start-up, and a failure there is a boot failure — which is fine, as long as it is deliberate and the error is legible. Next: turning a folder of packages into one repo without adding a framework.