denoland/deno · error · TypeError
ERR_INVALID_RETURN_VALUE
ERR_INVALID_RETURN_VALUE
Error message
Expected instance of Promise to be returned from the "promiseFn" function but got ${determineSpecificType(resultPromise)}. What it means
assert.rejects/assert.doesNotReject accept either a Promise or a function that must return one. waitForActual invokes the function and runs checkIsPromise on the result; if the return value lacks then/catch (not a thenable), it throws ERR_INVALID_RETURN_VALUE ('Expected instance of Promise to be returned from the "promiseFn" function') before anything is awaited. This mirrors Node.js's identical check in lib/assert.js.
Solutions
- Make the factory async so every path yields a promise: assert.rejects(async () => { ... }, expected).
- Or pass the promise directly instead of a factory: assert.rejects(fetch(url), expected).
- Audit the factory for branches that return without a value (implicit undefined) and return the promise from all paths.
- Keep the assertion and the async contract in sync when renaming or wrapping helpers.
Example fix
// before
assert.rejects(() => getUser(-1), { name: 'RangeError' }); // getUser is async, wrapper returns undefined
// after
assert.rejects(async () => getUser(-1), { name: 'RangeError' }); Defensive patterns
Strategy: type-guard
Validate before calling
const factory = () => getUser(-1);
const probe = factory();
if (!(probe instanceof Promise)) {
// fix the factory: make it async or return the promise directly
assert.rejects(async () => getUser(-1), expected);
} else {
assert.rejects(probe, expected);
} Type guard
const returnsPromise = (fn) => {
try { return fn() instanceof Promise; } catch { return false; }
}; Try / catch
try {
await assert.rejects(factory, expected);
} catch (e) {
if (e.code === 'ERR_INVALID_RETURN_VALUE') {
throw new Error('assert.rejects factory must return a Promise — make it async');
}
throw e;
} Prevention
- Default to async factories: assert.rejects(async () => ...).
- Or pass the promise itself: assert.rejects(doWork(), expected).
- Ensure every branch of the factory returns the promise — no implicit undefined returns.
When it happens
Trigger: assert.rejects(() => 42), assert.rejects(() => null), assert.rejects(() => { compute(); }) with a forgotten return, or a factory that conditionally returns a promise on some paths and undefined on others. The function is called exactly once and its return value is inspected immediately.
Common situations: Refactoring an async helper into a sync one and forgetting to update the test; passing a mapper/callback-style function instead of an async factory; omitting the async keyword on the arrow function so it returns the raw value; conditional returns where only the happy path returns the promise.
Related errors
- ERR_INVALID_ARG_TYPE
- ERR_AMBIGUOUS_ARGUMENT
- ERR_ASSERTION
- ERR_CONSTRUCT_CALL_REQUIRED
- ERR_INVALID_ARG_VALUE
AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20).
Data as JSON: /api/errors/bab939eafb329a3a.
Report an issue: GitHub.
Appendix: source
Thrown at ext/node/polyfills/assert.ts:353
// Accept native ES6 promises and promises that are implemented in a similar
// way. Do not accept thenables that use a function as `obj` and that have no
// `catch` handler.
return isPromise(obj) ||
(obj !== null && typeof obj === "object" &&
typeof obj.then === "function" &&
typeof obj.catch === "function");
}
async function waitForActual(
promiseFn,
) {
let resultPromise;
if (typeof promiseFn === "function") {
// Return a rejected promise if `promiseFn` throws synchronously.
resultPromise = promiseFn();
// Fail in case no promise is returned.
if (!checkIsPromise(resultPromise)) {
throw new ERR_INVALID_RETURN_VALUE(
"instance of Promise",
"promiseFn",
resultPromise,
);
}
} else if (checkIsPromise(promiseFn)) {
resultPromise = promiseFn;
} else {
throw new ERR_INVALID_ARG_TYPE(
"promiseFn",
["Function", "Promise"],
promiseFn,
);
}
try {
await resultPromise;
} catch (e) {View on GitHub (pinned to 9ad36f7a2c)