Skip to content

Commit c3b979c

Browse files
fixup: change async/thenable detection
1 parent 8bf7965 commit c3b979c

File tree

1 file changed

+40
-41
lines changed

1 file changed

+40
-41
lines changed

lib/internal/modules/esm/loader.js

Lines changed: 40 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)