Skip to content

Since v4.0, module instantiation errors can’t be caught because two errors are thrown if MODULARIZE=1 #24415

Closed
@FelixNumworks

Description

@FelixNumworks

Version of emscripten/emsdk: 4.0.8 (bug introduced in 4.0.0)

Description

I'm compiling with the MODULARIZE=1 flag and using code like the following to instantiate the module in js:

try {
  module = await Module({
    locateFile,
    fetchSettings,
  });
} catch (e) {
  console.warn(e);
  // redirect to error page
}

Before version 4.0, everything worked as expected: if the .wasm file failed to load, the error was caught and handled.

Since version 4.0, however, when the .wasm fetch fails, two errors are thrown in sequence. I can only catch one, but another one is thrown later and can't be caught because the catch block has already run.

Cause

This issue appears to originate from the following line in Module.js:

var wasmExports = await createWasm();

This line is generated by this one:

emscripten/tools/emscripten.py

Lines 1047 to 1052 in fe95793

if can_use_await():
# In modularize mode the generated code is within a factory function.
# This magic string gets replaced by `await createWasm`. It needed to allow
# closure and acorn to process the module without seeing this as a top-level
# await.
module.append("var wasmExports = EMSCRIPTEN$AWAIT(createWasm());\n")

This was introduced by this PR #23157

Here’s a minimal reproduction of the Module.js structure:

// Inside Module.js
function createWasm() {
  try {
    // code that throws error1
  } catch (error1) {
    readyPromiseReject(error1);
    var error2 = error1; // I'm adding this line to differentiate between the two
    return Promise.reject(error2); // <- error2 is caught, error1 is not
  }
}

var wasmExports = await createWasm();
// In my application code
try {
  await Module();
} catch (e) {
  console.warn(e); // Only error2 is caught here
}
// error1 is later thrown unhandled

So error2 is caught by user code, but error1 is thrown after the fact and cannot be intercepted, leading to an unhandled rejection or uncaught error.

Suggested Fix

Modifying the Module.js file this way fixes the problem:

try {
  var wasmExports = await createWasm();
} catch (err) {
  readyPromiseReject(err);
  return readyPromise;
}

This avoids throwing a second error, but it’s obviously not ideal.

The core issue is mixing a top-level await with a promise (readyPromise) that is also being rejected separately. This leads to two separate error flows. In my opinion, the code should use either top-level await or a returned/rejected promise—not both.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions