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
- Pass an actual iterable: Array, String, generator, async generator, or a (web) stream
- Wrap single values: ReadableStream.from([value])
- 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
- Guard loose 'value | iterable' API boundaries with an isIterable check
- Wrap single records in an array before passing them where a sequence is expected
- Prefer generators at producer boundaries — they always satisfy the iterable protocol
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
- ERR_INVALID_STATE
- ERR_INVALID_ARG_VALUE
- BenchContext::start() has already been invoked
- ${prefix}Linter plugin name must only contain lowercase lett
- ${prefix}Linter plugin name must start and end with a lowerc
AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20).
Data as JSON: /api/errors/77cbc188d87c33c6.
Report an issue: GitHub.