denoland/deno · error · Error

ERR_INVALID_STATE

ERR_INVALID_STATE

Error message

Invalid state: The iterator method must return an object

What it means

After getIterator() locates an iterator method, the WHATWG protocol requires calling it to return an iterator OBJECT. If obj[Symbol.iterator]() (or the async equivalent) returns a primitive (number, string, boolean) or null, Deno's node:stream/web machinery throws ERR_INVALID_STATE ('The iterator method must return an object') rather than proceeding.

Source

Thrown at ext/node/polyfills/internal/webstreams/util.js:249

      if (method == null) {
        const syncMethod = obj[SymbolIterator];
        if (syncMethod === undefined) {
          throw new ERR_ARG_NOT_ITERABLE(obj);
        }
        return createAsyncFromSyncIterator(
          getIterator(obj, "sync", syncMethod),
        );
      }
    } else {
      method = obj[SymbolIterator];
    }
  }
  if (method === undefined) {
    throw new ERR_ARG_NOT_ITERABLE(obj);
  }
  const iterator = FunctionPrototypeCall(method, obj);
  if (typeof iterator !== "object" || iterator === null) {
    throw new ERR_INVALID_STATE("The iterator method must return an object");
  }
  return { iterator, nextMethod: iterator.next, done: false };
}

function iteratorNext(iteratorRecord, value) {
  const result = value === undefined
    ? FunctionPrototypeCall(iteratorRecord.nextMethod, iteratorRecord.iterator)
    : FunctionPrototypeCall(
      iteratorRecord.nextMethod,
      iteratorRecord.iterator,
      value,
    );
  if (typeof result !== "object" || result === null) {
    throw new ERR_INVALID_STATE(
      "The iterator.next() method must return an object",
    );
  }
  return result;

View on GitHub (pinned to 9ad36f7a2c)

Solutions

  1. Make the iterator method return an object — the simplest fix is delegating to a generator: [Symbol.iterator]() { return this.values(); } where values is a generator
  2. Or return the object itself with a next() method: [Symbol.iterator]() { let i = 0; const self = this; return { next: () => ({ value: self.items[i], done: i++ >= self.items.length }) }; }
  3. Smoke-test custom iterables with [...obj] or Array.from(obj) before feeding them to streams

Example fix

// before
const seq = { [Symbol.iterator]: () => this.items }; // returns an array in wrong `this` context or a primitive

// after
const seq = { *[Symbol.iterator]() { yield* this.items; } };
Defensive patterns

Strategy: validation

Validate before calling

const iterFn = (source as any)?.[Symbol.iterator];
if (typeof iterFn !== 'function') throw new TypeError('not iterable');
const iter = iterFn.call(source);
if (typeof iter !== 'object' || iter === null) {
  throw new TypeError('Symbol.iterator must return an iterator object');
}
ReadableStream.from(source);

Type guard

function iteratorMethodIsSound(source: unknown): boolean {
  const fn = (source as any)?.[Symbol.iterator];
  if (typeof fn !== 'function') return false;
  const it = fn.call(source);
  return it != null && typeof it === 'object';
}

Try / catch

try {
  rs = ReadableStream.from(source);
} catch (e: any) {
  if (e?.code === 'ERR_INVALID_STATE' && /iterator method/.test(e.message)) {
    rs = ReadableStream.from([...makeIterable(source)]); // rebuild via generator
  } else throw e;
}

Prevention

When it happens

Trigger: A custom iterable whose [Symbol.iterator] returns a raw value: { [Symbol.iterator]: () => 42 }; an implementation returning this.value where value is a primitive; test mocks stubbing Symbol.iterator with () => null; a factory method that forgot 'return this' / 'return generator()'.

Common situations: Hand-rolled iterable protocol implementations; mocking libraries replacing Symbol.iterator with trivial stubs; refactoring an iterable class and dropping the iterator-returning statement.

Related errors


AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20). Data as JSON: /api/errors/dc90ec2ec378ab8b. Report an issue: GitHub.