@@ -36,12 +36,22 @@ const {
3636  getDefaultConditions, 
3737}  =  require ( 'internal/modules/esm/utils' ) ; 
3838const  {  kImplicitAssertType }  =  require ( 'internal/modules/esm/assert' ) ; 
39- const  {  ModuleWrap,  kEvaluating,  kEvaluated }  =  internalBinding ( 'module_wrap' ) ; 
39+ const  { 
40+   ModuleWrap, 
41+   kEvaluated, 
42+   kEvaluating, 
43+   kInstantiated, 
44+   throwIfPromiseRejected, 
45+ }  =  internalBinding ( 'module_wrap' ) ; 
4046const  { 
4147  urlToFilename, 
4248}  =  require ( 'internal/modules/helpers' ) ; 
4349let  defaultResolve ,  defaultLoad ,  defaultLoadSync ,  importMetaInitializer ; 
4450
51+ let  debug  =  require ( 'internal/util/debuglog' ) . debuglog ( 'esm' ,  ( fn )  =>  { 
52+   debug  =  fn ; 
53+ } ) ; 
54+ 
4555/** 
4656 * @typedef  {import('./hooks.js').HooksProxy } HooksProxy 
4757 * @typedef  {import('./module_job.js').ModuleJobBase } ModuleJobBase 
@@ -75,6 +85,23 @@ function getTranslators() {
7585  return  translators ; 
7686} 
7787
88+ /** 
89+  * Generate message about potential race condition caused by requiring a cached module that has started 
90+  * async linking. 
91+  * @param  {string } filename Filename of the module being required. 
92+  * @param  {string|undefined } parentFilename Filename of the module calling require(). 
93+  * @returns  {string } Error message. 
94+  */ 
95+ function  getRaceMessage ( filename ,  parentFilename )  { 
96+   let  raceMessage  =  `Cannot require() ES Module ${ filename }   because it is not yet fully loaded. ` ; 
97+   raceMessage  +=  'This may be caused by a race condition if the module is simultaneously dynamically ' ; 
98+   raceMessage  +=  'import()-ed via Promise.all(). Try await-ing the import() sequentially in a loop instead.' ; 
99+   if  ( parentFilename )  { 
100+     raceMessage  +=  ` (from ${ parentFilename }  )` ; 
101+   } 
102+   return  raceMessage ; 
103+ } 
104+ 
78105/** 
79106 * @type  {HooksProxy } 
80107 * Multiple loader instances exist for various, specific reasons (see code comments at site). 
@@ -297,35 +324,53 @@ class ModuleLoader {
297324    //    evaluated at this point. 
298325    // TODO(joyeecheung): add something similar to CJS loader's requireStack to help 
299326    // debugging the the problematic links in the graph for import. 
327+     debug ( 'importSyncForRequire' ,  parent ?. filename ,  '->' ,  filename ,  job ) ; 
300328    if  ( job  !==  undefined )  { 
301329      mod [ kRequiredModuleSymbol ]  =  job . module ; 
302330      const  parentFilename  =  urlToFilename ( parent ?. filename ) ; 
303331      // TODO(node:55782): this race may stop to happen when the ESM resolution and loading become synchronous. 
304332      if  ( ! job . module )  { 
305-         let  message  =  `Cannot require() ES Module ${ filename }   because it is not yet fully loaded. ` ; 
306-         message  +=  'This may be caused by a race condition if the module is simultaneously dynamically ' ; 
307-         message  +=  'import()-ed via Promise.all(). Try await-ing the import() sequentially in a loop instead.' ; 
308-         if  ( parentFilename )  { 
309-           message  +=  ` (from ${ parentFilename }  )` ; 
310-         } 
311-         assert ( job . module ,  message ) ; 
333+         assert . fail ( getRaceMessage ( filename ,  parentFilename ) ) ; 
312334      } 
313335      if  ( job . module . async )  { 
314336        throw  new  ERR_REQUIRE_ASYNC_MODULE ( filename ,  parentFilename ) ; 
315337      } 
316-       // job.module may be undefined if it's asynchronously loaded. Which means 
317-       // there is likely a cycle. 
318-       if  ( job . module . getStatus ( )  !==  kEvaluated )  { 
319-         let  message  =  `Cannot require() ES Module ${ filename }   in a cycle.` ; 
320-         if  ( parentFilename )  { 
321-           message  +=  ` (from ${ parentFilename }  )` ; 
322-         } 
323-         message  +=  'A cycle involving require(esm) is disallowed to maintain ' ; 
324-         message  +=  'invariants madated by the ECMAScript specification' ; 
325-         message  +=  'Try making at least part of the dependency in the graph lazily loaded.' ; 
326-         throw  new  ERR_REQUIRE_CYCLE_MODULE ( message ) ; 
338+       const  status  =  job . module . getStatus ( ) ; 
339+       debug ( 'Module status' ,  filename ,  status ) ; 
340+       if  ( status  ===  kEvaluated )  { 
341+         return  {  wrap : job . module ,  namespace : job . module . getNamespaceSync ( filename ,  parentFilename )  } ; 
342+       }  else  if  ( status  ===  kInstantiated )  { 
343+         // When it's an async job cached by another import request, 
344+         // which has finished linking but has not started its 
345+         // evaluation because the async run() task would be later 
346+         // in line. Then start the evaluation now with runSync(), which 
347+         // is guaranteed to finish by the time the other run() get to it, 
348+         // and the other task would just get the cached evaluation results, 
349+         // similar to what would happen when both are async. 
350+         mod [ kRequiredModuleSymbol ]  =  job . module ; 
351+         const  {  namespace }  =  job . runSync ( parent ) ; 
352+         return  {  wrap : job . module ,  namespace : namespace  ||  job . module . getNamespace ( )  } ; 
327353      } 
328-       return  {  wrap : job . module ,  namespace : job . module . getNamespaceSync ( filename ,  parentFilename )  } ; 
354+       // When the cached async job have already encountered a linking 
355+       // error that gets wrapped into a rejection, but is still later 
356+       // in line to throw on it, just unwrap and throw the linking error 
357+       // from require(). 
358+       if  ( job . instantiated )  { 
359+         throwIfPromiseRejected ( job . instantiated ) ; 
360+       } 
361+       if  ( status  !==  kEvaluating )  { 
362+         assert . fail ( `Unexpected module status ${ status }  . `  + 
363+                     getRaceMessage ( filename ,  parentFilename ) ) ; 
364+       } 
365+       let  message  =  `Cannot require() ES Module ${ filename }   in a cycle.` ; 
366+       if  ( parentFilename )  { 
367+         message  +=  ` (from ${ parentFilename }  )` ; 
368+       } 
369+       message  +=  'A cycle involving require(esm) is disallowed to maintain ' ; 
370+       message  +=  'invariants madated by the ECMAScript specification' ; 
371+       message  +=  'Try making at least part of the dependency in the graph lazily loaded.' ; 
372+       throw  new  ERR_REQUIRE_CYCLE_MODULE ( message ) ; 
373+ 
329374    } 
330375    // TODO(joyeecheung): refactor this so that we pre-parse in C++ and hit the 
331376    // cache here, or use a carrier object to carry the compiled module script 
0 commit comments