Skip to content
Closed
Prev Previous commit
module: throw error when re-runing errored module jobs
Re-evaluating an errored ESM should lead to rejecting
the rejection again - this is also the case when importing
it twice. In the case of retrying with
require after import, just throw the cached error.

Drive-by: add some debug logs.
PR-URL: #58957
Fixes: #58945
Reviewed-By: Marco Ippolito <marcoippolito54@gmail.com>
Reviewed-By: Luigi Pinca <luigipinca@gmail.com>
Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
  • Loading branch information
joyeecheung authored and marco-ippolito committed Aug 20, 2025
commit bd6691a3bd9e4df2d1cce9cd0e43807bfb91e4f9
4 changes: 4 additions & 0 deletions lib/internal/modules/esm/loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ const {
kEvaluated,
kEvaluating,
kInstantiated,
kErrored,
throwIfPromiseRejected,
} = internalBinding('module_wrap');
const {
Expand Down Expand Up @@ -350,6 +351,9 @@ class ModuleLoader {
mod[kRequiredModuleSymbol] = job.module;
const { namespace } = job.runSync(parent);
return { wrap: job.module, namespace: namespace || job.module.getNamespace() };
} else if (status === kErrored) {
// If the module was previously imported and errored, throw the error.
throw job.module.getError();
}
// When the cached async job have already encountered a linking
// error that gets wrapped into a rejection, but is still later
Expand Down
10 changes: 8 additions & 2 deletions lib/internal/modules/esm/module_job.js
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,7 @@ class ModuleJob extends ModuleJobBase {
assert(this.module instanceof ModuleWrap);
let status = this.module.getStatus();

debug('ModuleJob.runSync', this.module);
debug('ModuleJob.runSync()', status, this.module);
// FIXME(joyeecheung): this cannot fully handle < kInstantiated. Make the linking
// fully synchronous instead.
if (status === kUninstantiated) {
Expand Down Expand Up @@ -316,6 +316,7 @@ class ModuleJob extends ModuleJobBase {
}

async run() {
debug('ModuleJob.run()', this.module);
await this.instantiate();
const timeout = -1;
const breakOnSigint = false;
Expand Down Expand Up @@ -392,7 +393,11 @@ class ModuleJobSync extends ModuleJobBase {
async run() {
// This path is hit by a require'd module that is imported again.
const status = this.module.getStatus();
if (status > kInstantiated) {
debug('ModuleJobSync.run()', status, this.module);
// If the module was previously required and errored, reject from import() again.
if (status === kErrored) {
throw this.module.getError();
} else if (status > kInstantiated) {
if (this.evaluationPromise) {
await this.evaluationPromise;
}
Expand All @@ -413,6 +418,7 @@ class ModuleJobSync extends ModuleJobBase {
}

runSync(parent) {
debug('ModuleJobSync.runSync()', this.module);
// TODO(joyeecheung): add the error decoration logic from the async instantiate.
this.module.async = this.module.instantiateSync();
// If --experimental-print-required-tla is true, proceeds to evaluation even
Expand Down
17 changes: 17 additions & 0 deletions test/es-module/test-import-module-retry-require-errored.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// This tests that after failing to import an ESM that rejects,
// retrying with require() still throws.

'use strict';
const common = require('../common');
const assert = require('assert');

(async () => {
await assert.rejects(import('../fixtures/es-modules/throw-error.mjs'), {
message: 'test',
});
assert.throws(() => {
require('../fixtures/es-modules/throw-error.mjs');
}, {
message: 'test',
});
})().catch(common.mustNotCall());
16 changes: 16 additions & 0 deletions test/es-module/test-require-module-retry-import-errored-2.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// This tests that after failing to require an ESM that throws,
// retrying with import() still rejects.

'use strict';
const common = require('../common');
const assert = require('assert');

assert.throws(() => {
require('../fixtures/es-modules/throw-error.mjs');
}, {
message: 'test',
});

assert.rejects(import('../fixtures/es-modules/throw-error.mjs'), {
message: 'test',
}).catch(common.mustNotCall());
Loading