denoland/deno · error · TypeError

ERR_ARG_NOT_ITERABLE

ERR_ARG_NOT_ITERABLE

Error message

${obj} must be iterable

What it means

Raised by getIterator(obj, 'async') in ext/node/polyfills/internal/webstreams/util.js, the iteration helper behind node:stream/web consumption of iterables (e.g. ReadableStream.from(source)). For async iteration it first looks up Symbol.asyncIterator; if that is null/undefined it falls back to Symbol.iterator (wrapped as async-from-sync). An object exposing NEITHER symbol is not iterable and throws ERR_ARG_NOT_ITERABLE with the inspected value in the message.

Source

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

    [SymbolAsyncIterator]() {
      return this;
    },
  };
  return {
    iterator: asyncIterator,
    nextMethod: asyncIterator.next,
    done: false,
  };
}

function getIterator(obj, kind = "sync", method) {
  if (method === undefined) {
    if (kind === "async") {
      method = obj[SymbolAsyncIterator];
      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 };
}

View on GitHub (pinned to 9ad36f7a2c)

Solutions

  1. Pass an actual iterable: Array, String, generator, async generator, or a (web) stream
  2. Wrap single values: ReadableStream.from([value])
  3. Guard the boundary: check typeof obj?.[Symbol.iterator] === 'function' || typeof obj?.[Symbol.asyncIterator] === 'function' before calling

Example fix

// before
const rs = ReadableStream.from(source); // source may be a number/plain object

// after
const isIterable = (v) => v != null &&
  (typeof v[Symbol.iterator] === 'function' ||
   typeof v[Symbol.asyncIterator] === 'function');
const rs = ReadableStream.from(isIterable(source) ? source : [source]);
Defensive patterns

Strategy: type-guard

Validate before calling

const isAsyncIterable = (v: unknown) =>
  v != null && typeof (v as any)[Symbol.asyncIterator] === 'function';
const isIterable = (v: unknown) =>
  v != null && typeof (v as any)[Symbol.iterator] === 'function';
const src = isAsyncIterable(source) || isIterable(source) ? source : [source];
const rs = ReadableStream.from(src);

Type guard

function isIterableOrAsyncIterable(
  v: unknown,
): v is Iterable<unknown> | AsyncIterable<unknown> {
  return v != null &&
    (typeof (v as any)[Symbol.iterator] === 'function' ||
      typeof (v as any)[Symbol.asyncIterator] === 'function');
}

Try / catch

try {
  rs = ReadableStream.from(source);
} catch (e: any) {
  if (e?.code === 'ERR_ARG_NOT_ITERABLE') rs = ReadableStream.from([source]);
  else throw e;
}

Prevention

When it happens

Trigger: ReadableStream.from(42), ReadableStream.from({}), or ReadableStream.from(null) via node:stream/web; a factory that accepts 'stream or iterable or value' and a raw value (number, boolean, plain object) slips through; JSON-parsed data assumed to be an iterable list.

Common situations: API boundaries where the argument type is loose (config loaders, job queues); passing a parsed single record instead of the records array; async pipeline code copied from samples that assumed an array.

Related errors


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