@@ -18,7 +18,6 @@ const {
1818 kIsExecuting,
1919 kRequiredModuleSymbol,
2020} = require ( 'internal/modules/cjs/loader' ) ;
21-
2221const { imported_cjs_symbol } = internalBinding ( 'symbols' ) ;
2322
2423const assert = require ( 'internal/assert' ) ;
@@ -38,7 +37,13 @@ const {
3837 forceDefaultLoader,
3938} = require ( 'internal/modules/esm/utils' ) ;
4039const { kImplicitTypeAttribute } = require ( 'internal/modules/esm/assert' ) ;
41- const { ModuleWrap, kEvaluating, kEvaluated } = internalBinding ( 'module_wrap' ) ;
40+ const {
41+ ModuleWrap,
42+ kEvaluated,
43+ kEvaluating,
44+ kInstantiated,
45+ throwIfPromiseRejected,
46+ } = internalBinding ( 'module_wrap' ) ;
4247const {
4348 urlToFilename,
4449} = require ( 'internal/modules/helpers' ) ;
@@ -53,6 +58,10 @@ let defaultResolve, defaultLoad, defaultLoadSync, importMetaInitializer;
5358const { tracingChannel } = require ( 'diagnostics_channel' ) ;
5459const onImport = tracingChannel ( 'module.import' ) ;
5560
61+ let debug = require ( 'internal/util/debuglog' ) . debuglog ( 'esm' , ( fn ) => {
62+ debug = fn ;
63+ } ) ;
64+
5665/**
5766 * @typedef {import('./hooks.js').HooksProxy } HooksProxy
5867 * @typedef {import('./module_job.js').ModuleJobBase } ModuleJobBase
@@ -86,6 +95,23 @@ function getTranslators() {
8695 return translators ;
8796}
8897
98+ /**
99+ * Generate message about potential race condition caused by requiring a cached module that has started
100+ * async linking.
101+ * @param {string } filename Filename of the module being required.
102+ * @param {string|undefined } parentFilename Filename of the module calling require().
103+ * @returns {string } Error message.
104+ */
105+ function getRaceMessage ( filename , parentFilename ) {
106+ let raceMessage = `Cannot require() ES Module ${ filename } because it is not yet fully loaded. ` ;
107+ raceMessage += 'This may be caused by a race condition if the module is simultaneously dynamically ' ;
108+ raceMessage += 'import()-ed via Promise.all(). Try await-ing the import() sequentially in a loop instead.' ;
109+ if ( parentFilename ) {
110+ raceMessage += ` (from ${ parentFilename } )` ;
111+ }
112+ return raceMessage ;
113+ }
114+
89115/**
90116 * @type {HooksProxy }
91117 * Multiple loader instances exist for various, specific reasons (see code comments at site).
@@ -340,35 +366,53 @@ class ModuleLoader {
340366 // evaluated at this point.
341367 // TODO(joyeecheung): add something similar to CJS loader's requireStack to help
342368 // debugging the the problematic links in the graph for import.
369+ debug ( 'importSyncForRequire' , parent ?. filename , '->' , filename , job ) ;
343370 if ( job !== undefined ) {
344371 mod [ kRequiredModuleSymbol ] = job . module ;
345372 const parentFilename = urlToFilename ( parent ?. filename ) ;
346373 // TODO(node:55782): this race may stop to happen when the ESM resolution and loading become synchronous.
347374 if ( ! job . module ) {
348- let message = `Cannot require() ES Module ${ filename } because it is not yet fully loaded. ` ;
349- message += 'This may be caused by a race condition if the module is simultaneously dynamically ' ;
350- message += 'import()-ed via Promise.all(). Try await-ing the import() sequentially in a loop instead.' ;
351- if ( parentFilename ) {
352- message += ` (from ${ parentFilename } )` ;
353- }
354- assert ( job . module , message ) ;
375+ assert . fail ( getRaceMessage ( filename , parentFilename ) ) ;
355376 }
356377 if ( job . module . async ) {
357378 throw new ERR_REQUIRE_ASYNC_MODULE ( filename , parentFilename ) ;
358379 }
359- // job.module may be undefined if it's asynchronously loaded. Which means
360- // there is likely a cycle.
361- if ( job . module . getStatus ( ) !== kEvaluated ) {
362- let message = `Cannot require() ES Module ${ filename } in a cycle.` ;
363- if ( parentFilename ) {
364- message += ` (from ${ parentFilename } )` ;
365- }
366- message += 'A cycle involving require(esm) is disallowed to maintain ' ;
367- message += 'invariants madated by the ECMAScript specification' ;
368- message += 'Try making at least part of the dependency in the graph lazily loaded.' ;
369- throw new ERR_REQUIRE_CYCLE_MODULE ( message ) ;
380+ const status = job . module . getStatus ( ) ;
381+ debug ( 'Module status' , filename , status ) ;
382+ if ( status === kEvaluated ) {
383+ return { wrap : job . module , namespace : job . module . getNamespaceSync ( filename , parentFilename ) } ;
384+ } else if ( status === kInstantiated ) {
385+ // When it's an async job cached by another import request,
386+ // which has finished linking but has not started its
387+ // evaluation because the async run() task would be later
388+ // in line. Then start the evaluation now with runSync(), which
389+ // is guaranteed to finish by the time the other run() get to it,
390+ // and the other task would just get the cached evaluation results,
391+ // similar to what would happen when both are async.
392+ mod [ kRequiredModuleSymbol ] = job . module ;
393+ const { namespace } = job . runSync ( parent ) ;
394+ return { wrap : job . module , namespace : namespace || job . module . getNamespace ( ) } ;
370395 }
371- return { wrap : job . module , namespace : job . module . getNamespaceSync ( filename , parentFilename ) } ;
396+ // When the cached async job have already encountered a linking
397+ // error that gets wrapped into a rejection, but is still later
398+ // in line to throw on it, just unwrap and throw the linking error
399+ // from require().
400+ if ( job . instantiated ) {
401+ throwIfPromiseRejected ( job . instantiated ) ;
402+ }
403+ if ( status !== kEvaluating ) {
404+ assert . fail ( `Unexpected module status ${ status } . ` +
405+ getRaceMessage ( filename , parentFilename ) ) ;
406+ }
407+ let message = `Cannot require() ES Module ${ filename } in a cycle.` ;
408+ if ( parentFilename ) {
409+ message += ` (from ${ parentFilename } )` ;
410+ }
411+ message += 'A cycle involving require(esm) is disallowed to maintain ' ;
412+ message += 'invariants madated by the ECMAScript specification' ;
413+ message += 'Try making at least part of the dependency in the graph lazily loaded.' ;
414+ throw new ERR_REQUIRE_CYCLE_MODULE ( message ) ;
415+
372416 }
373417 // TODO(joyeecheung): refactor this so that we pre-parse in C++ and hit the
374418 // cache here, or use a carrier object to carry the compiled module script
0 commit comments