@@ -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