Skip to content

Commit ab935b6

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

File tree

1 file changed

+43
-42
lines changed

1 file changed

+43
-42
lines changed

lib/internal/modules/esm/loader.js

Lines changed: 43 additions & 42 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,
@@ -109,12 +111,14 @@ let emittedSpecifierResolutionWarning = false;
109111
* position in the hook chain.
110112
* @param {string} meta.hookName The kind of hook the chain is (ex 'resolve')
111113
* @param {boolean} meta.shortCircuited Whether a hook signaled a short-circuit.
112-
* @param {(hookErrIdentifier, hookArgs) => void} validate A wrapper function
114+
* @param {object} validators A wrapper function
113115
* containing all validation of a custom loader hook's intermediary output. Any
114116
* validation within MUST throw.
117+
* @param {(hookErrIdentifier, hookArgs) => void} validateArgs
118+
* @param {(hookErrIdentifier, output) => void} validateOutput
115119
* @returns {function next<HookName>(...hookArgs)} The next hook in the chain.
116120
*/
117-
function nextHookFactory(chain, meta, validate) {
121+
function nextHookFactory(chain, meta, { validateArgs, validateOutput }) {
118122
// First, prepare the current
119123
const { hookName } = meta;
120124
const {
@@ -137,7 +141,7 @@ function nextHookFactory(chain, meta, validate) {
137141
// factory generates the next link in the chain.
138142
meta.hookIndex--;
139143

140-
nextNextHook = nextHookFactory(chain, meta, validate);
144+
nextNextHook = nextHookFactory(chain, meta, { validateArgs, validateOutput });
141145
} else {
142146
// eslint-disable-next-line func-name-matching
143147
nextNextHook = function chainAdvancedTooFar() {
@@ -152,34 +156,25 @@ function nextHookFactory(chain, meta, validate) {
152156
// Update only when hook is invoked to avoid fingering the wrong filePath
153157
meta.hookErrIdentifier = `${hookFilePath} '${hookName}'`;
154158

155-
validate(`${meta.hookErrIdentifier} hook's ${nextHookName}()`, args);
159+
validateArgs(`${meta.hookErrIdentifier} hook's ${nextHookName}()`, args);
160+
161+
const outputErrIdentifier = `${chain[generatedHookIndex].url} '${hookName}' hook's ${nextHookName}()`;
156162

157163
// Set when next<HookName> is actually called, not just generated.
158164
if (generatedHookIndex === 0) { meta.chainFinished = true; }
159165

160166
ArrayPrototypePush(args, nextNextHook);
161-
const output = ReflectApply(hook, undefined, args);
167+
let output = ReflectApply(hook, undefined, args);
168+
169+
validateOutput(outputErrIdentifier, output);
162170

163171
function checkShortCircuited(output) {
164172
if (output?.shortCircuit === true) { meta.shortCircuited = true; }
165173
}
166174

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-
]);
175+
if (meta.isChainAsync) {
176+
output = PromiseResolve(output);
177+
PromisePrototypeThen(output, checkShortCircuited);
183178
} else {
184179
checkShortCircuited(output);
185180
}
@@ -586,7 +581,7 @@ class ESMLoader {
586581
shortCircuited: false,
587582
};
588583

589-
const validate = (hookErrIdentifier, { 0: nextUrl, 1: ctx }) => {
584+
const validateArgs = (hookErrIdentifier, { 0: nextUrl, 1: ctx }) => {
590585
if (typeof nextUrl !== 'string') {
591586
// non-strings can be coerced to a url string
592587
// validateString() throws a less-specific error
@@ -612,19 +607,22 @@ class ESMLoader {
612607

613608
validateObject(ctx, `${hookErrIdentifier} context`);
614609
};
610+
const validateOutput = (hookErrIdentifier, output) => {
611+
if (typeof output !== 'object') { // [2]
612+
throw new ERR_INVALID_RETURN_VALUE(
613+
'an object',
614+
hookErrIdentifier,
615+
output,
616+
);
617+
}
618+
};
615619

616-
const nextLoad = nextHookFactory(chain, meta, validate);
620+
const nextLoad = nextHookFactory(chain, meta, { validateArgs, validateOutput });
617621

618622
const loaded = await nextLoad(url, context);
619623
const { hookErrIdentifier } = meta; // Retrieve the value after all settled
620624

621-
if (typeof loaded !== 'object') { // [2]
622-
throw new ERR_INVALID_RETURN_VALUE(
623-
'an object',
624-
hookErrIdentifier,
625-
loaded,
626-
);
627-
}
625+
validateOutput(hookErrIdentifier, loaded);
628626

629627
if (loaded?.shortCircuit === true) { meta.shortCircuited = true; }
630628

@@ -837,7 +835,7 @@ class ESMLoader {
837835
parentURL,
838836
};
839837

840-
const validate = (hookErrIdentifier, output) => {
838+
const validateArgs = (hookErrIdentifier, output) => {
841839
const { 0: suppliedSpecifier, 1: ctx } = output;
842840

843841
validateString(
@@ -847,22 +845,25 @@ class ESMLoader {
847845

848846
validateObject(ctx, `${hookErrIdentifier} context`);
849847
};
848+
const validateOutput = (hookErrIdentifier, output) => {
849+
if (
850+
typeof output !== 'object' || // [2]
851+
typeof output.then === 'function'
852+
) {
853+
throw new ERR_INVALID_RETURN_VALUE(
854+
'an object',
855+
hookErrIdentifier,
856+
output,
857+
);
858+
}
859+
};
850860

851-
const nextResolve = nextHookFactory(chain, meta, validate);
861+
const nextResolve = nextHookFactory(chain, meta, { validateArgs, validateOutput });
852862

853863
const resolution = nextResolve(originalSpecifier, context);
854864
const { hookErrIdentifier } = meta; // Retrieve the value after all settled
855865

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-
}
866+
validateOutput(hookErrIdentifier, resolution);
866867

867868
if (resolution?.shortCircuit === true) { meta.shortCircuited = true; }
868869

0 commit comments

Comments
 (0)