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

  1. Make the factory async so every path yields a promise: assert.rejects(async () => { ... }, expected).
  2. Or pass the promise directly instead of a factory: assert.rejects(fetch(url), expected).
  3. Audit the factory for branches that return without a value (implicit undefined) and return the promise from all paths.
  4. 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

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


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)