Search code examples
javascriptnode.jses6-modulescommonjsevent-loop

Precedence of setImmediate() and setTimeout() callbacks in CommonJS vs ESM


Calling setTimeout with setImmediate has unpredicted behaviour if using CommonJS modules. But if you switch to ESM ("type": "module" in package.json), it will always execute setImmediate before setTimeout.

setTimeout(() => {
  console.log('timeout');
}, 0);

setImmediate(() => {
  console.log('immediate');
});

Why does this happen? Is it related to the fact that in ESM modules are loaded asynchronously?


Solution

  • The thing is that as determined in prior Q/As, like 1, 2, 3, 4, 5, 6 and many more (thanks Bergi for the list), with the best "in-depth" explanation being maybe done in this issue comment; the indeterminate order of setImmediate() and setTimeout() only happens when calls are made from the "main module".

    When ran as ESM, the main script is actually always delayed by one task because of an async call to OpenFileHandle, which is truly asynchronous, and not just spawning a new microtask.

    Here is the actual JS trace that leads to there:

    Trace:
        at open (node:internal/fs/promises:633:11)
        at readFile (node:internal/fs/promises:1243:20)
        at getSource (node:internal/modules/esm/load:46:20)
        at defaultLoad (node:internal/modules/esm/load:137:40)
        at ModuleLoader.load (node:internal/modules/esm/loader:404:13)
        at ModuleLoader.moduleProvider (node:internal/modules/esm/loader:286:56)
        at new ModuleJob (node:internal/modules/esm/module_job:65:26)
        at #createModuleJob (node:internal/modules/esm/loader:298:17)
        at ModuleLoader.getJobFromResolveResult (node:internal/modules/esm/loader:255:34)
        at ModuleLoader.getModuleJob (node:internal/modules/esm/loader:235:17)
    

    Source of ModuleLoader.getModuleJob if you want to walk that up.

    So we're actually out of the aforementioned "main module", but instead in the I/O phase and now the order is deterministic, immediate, then timeout.