@@ -15,6 +15,8 @@ const {
1515 ObjectDefineProperty,
1616 ObjectSetPrototypeOf,
1717 PromiseAll,
18+ PromiseResolve,
19+ PromisePrototypeThen,
1820 ReflectApply,
1921 RegExpPrototypeExec,
2022 SafeArrayIterator,
@@ -114,7 +116,7 @@ let emittedSpecifierResolutionWarning = false;
114116 * validation within MUST throw.
115117 * @returns {function next<HookName>(...hookArgs) } The next hook in the chain.
116118 */
117- function nextHookFactory ( chain , meta , validate ) {
119+ function nextHookFactory ( chain , meta , { validateArgs , validateOutput } ) {
118120 // First, prepare the current
119121 const { hookName } = meta ;
120122 const {
@@ -137,7 +139,7 @@ function nextHookFactory(chain, meta, validate) {
137139 // factory generates the next link in the chain.
138140 meta . hookIndex -- ;
139141
140- nextNextHook = nextHookFactory ( chain , meta , validate ) ;
142+ nextNextHook = nextHookFactory ( chain , meta , { validateArgs , validateOutput } ) ;
141143 } else {
142144 // eslint-disable-next-line func-name-matching
143145 nextNextHook = function chainAdvancedTooFar ( ) {
@@ -152,34 +154,25 @@ function nextHookFactory(chain, meta, validate) {
152154 // Update only when hook is invoked to avoid fingering the wrong filePath
153155 meta . hookErrIdentifier = `${ hookFilePath } '${ hookName } '` ;
154156
155- validate ( `${ meta . hookErrIdentifier } hook's ${ nextHookName } ()` , args ) ;
157+ validateArgs ( `${ meta . hookErrIdentifier } hook's ${ nextHookName } ()` , args ) ;
158+
159+ const outputErrIdentifier = `${ chain [ generatedHookIndex ] . url } '${ hookName } ' hook's ${ nextHookName } ()` ;
156160
157161 // Set when next<HookName> is actually called, not just generated.
158162 if ( generatedHookIndex === 0 ) { meta . chainFinished = true ; }
159163
160164 ArrayPrototypePush ( args , nextNextHook ) ;
161- const output = ReflectApply ( hook , undefined , args ) ;
165+ let output = ReflectApply ( hook , undefined , args ) ;
166+
167+ validateOutput ( output , outputErrIdentifier ) ;
162168
163169 function checkShortCircuited ( output ) {
164170 if ( output ?. shortCircuit === true ) { meta . shortCircuited = true ; }
165171 }
166172
167- const then = output ?. then ;
168- if ( typeof then === 'function' ) {
169- if ( ! meta . isChainAsync ) {
170- throw ERR_INVALID_RETURN_VALUE (
171- 'an object' ,
172- // MUST use generatedHookIndex because the chain has already advanced,
173- // causing meta.hookIndex to advance
174- `${ chain [ generatedHookIndex ] . url } '${ hookName } ' hook's ${ nextHookName } ()` ,
175- output ,
176- ) ;
177- }
178-
179- ReflectApply ( then , output , [
180- checkShortCircuited ,
181- // TODO: handle error case
182- ] ) ;
173+ if ( meta . isChainAsync ) {
174+ output = PromiseResolve ( output ) ;
175+ PromisePrototypeThen ( output , checkShortCircuited ) ;
183176 } else {
184177 checkShortCircuited ( output ) ;
185178 }
@@ -586,7 +579,7 @@ class ESMLoader {
586579 shortCircuited : false ,
587580 } ;
588581
589- const validate = ( hookErrIdentifier , { 0 : nextUrl , 1 : ctx } ) => {
582+ const validateArgs = ( hookErrIdentifier , { 0 : nextUrl , 1 : ctx } ) => {
590583 if ( typeof nextUrl !== 'string' ) {
591584 // non-strings can be coerced to a url string
592585 // validateString() throws a less-specific error
@@ -612,19 +605,22 @@ class ESMLoader {
612605
613606 validateObject ( ctx , `${ hookErrIdentifier } context` ) ;
614607 } ;
608+ const validateOutput = ( output , hookErrIdentifier ) => {
609+ if ( typeof output !== 'object' ) { // [2]
610+ throw new ERR_INVALID_RETURN_VALUE (
611+ 'an object' ,
612+ hookErrIdentifier ,
613+ output ,
614+ ) ;
615+ }
616+ } ;
615617
616- const nextLoad = nextHookFactory ( chain , meta , validate ) ;
618+ const nextLoad = nextHookFactory ( chain , meta , { validateArgs , validateOutput } ) ;
617619
618620 const loaded = await nextLoad ( url , context ) ;
619621 const { hookErrIdentifier } = meta ; // Retrieve the value after all settled
620622
621- if ( typeof loaded !== 'object' ) { // [2]
622- throw new ERR_INVALID_RETURN_VALUE (
623- 'an object' ,
624- hookErrIdentifier ,
625- loaded ,
626- ) ;
627- }
623+ validateOutput ( loaded , hookErrIdentifier ) ;
628624
629625 if ( loaded ?. shortCircuit === true ) { meta . shortCircuited = true ; }
630626
@@ -837,7 +833,7 @@ class ESMLoader {
837833 parentURL,
838834 } ;
839835
840- const validate = ( hookErrIdentifier , output ) => {
836+ const validateArgs = ( hookErrIdentifier , output ) => {
841837 const { 0 : suppliedSpecifier , 1 : ctx } = output ;
842838
843839 validateString (
@@ -847,22 +843,25 @@ class ESMLoader {
847843
848844 validateObject ( ctx , `${ hookErrIdentifier } context` ) ;
849845 } ;
846+ const validateOutput = ( output , hookErrIdentifier ) => {
847+ if (
848+ typeof output !== 'object' || // [2]
849+ typeof output . then === 'function'
850+ ) {
851+ throw new ERR_INVALID_RETURN_VALUE (
852+ 'an object' ,
853+ hookErrIdentifier ,
854+ output ,
855+ ) ;
856+ }
857+ } ;
850858
851- const nextResolve = nextHookFactory ( chain , meta , validate ) ;
859+ const nextResolve = nextHookFactory ( chain , meta , { validateArgs , validateOutput } ) ;
852860
853861 const resolution = nextResolve ( originalSpecifier , context ) ;
854862 const { hookErrIdentifier } = meta ; // Retrieve the value after all settled
855863
856- if (
857- typeof resolution !== 'object' || // [2]
858- typeof resolution ?. then === 'function'
859- ) {
860- throw new ERR_INVALID_RETURN_VALUE (
861- 'an object' ,
862- hookErrIdentifier ,
863- resolution ,
864- ) ;
865- }
864+ validateOutput ( resolution , hookErrIdentifier ) ;
866865
867866 if ( resolution ?. shortCircuit === true ) { meta . shortCircuited = true ; }
868867
0 commit comments