This error occurs when a custom iterator's next() method returns a non-object value instead of the required {value, done} object. Common in stream implementations and async iterators.
This TypeError is thrown when a custom iterator implementation violates the JavaScript iterator protocol. According to the iterator protocol, the next() method must return an object with two properties: 'value' (the current item) and 'done' (a boolean indicating if iteration is complete). In Node.js streams, this error frequently appears when implementing custom async iterators or when using ReadableStream.from() with an improperly implemented iterable. The stream implementation expects iterator results to be objects, but receives null, undefined, or primitive values instead. The error occurs at the JavaScript engine level when it validates the return value of an iterator's next() method during iteration. This is a fundamental requirement of the iterator protocol that applies to both synchronous and asynchronous iterators.
Check that your custom iterator's next() method always returns an object with the required structure:
// Incorrect - returns primitive
const badIterator = {
current: 0,
next() {
return this.current++; // ❌ Returns number
}
};
// Correct - returns iterator result object
const goodIterator = {
current: 0,
next() {
const value = this.current++;
const done = value >= 10;
return { value, done }; // ✅ Returns object
}
};Ensure both value and done properties are present in the returned object.
For async iterators, ensure the Promise resolves to a valid iterator result object:
// Incorrect async iterator
async function* badAsyncGen() {
yield null; // ❌ Yields null
}
// Correct async iterator
async function* goodAsyncGen() {
yield { value: 'data', done: false }; // ✅ Yields object
// OR simply yield the value - generator handles wrapping
yield 'data'; // ✅ Also valid in generators
}
// For custom async iterators (not generators)
const asyncIterator = {
async next() {
return { value: await fetchData(), done: false }; // ✅ Returns object
}
};Generator functions automatically wrap yielded values, but custom iterators must return the full object structure.
When implementing custom streams with iterators, ensure proper object returns:
const { Readable } = require('stream');
// Incorrect - returns raw values
const badStream = Readable.from({
[Symbol.asyncIterator]() {
let count = 0;
return {
async next() {
if (count++ < 3) return 'data'; // ❌ Returns string
return null; // ❌ Returns null
}
};
}
});
// Correct - returns proper iterator result objects
const goodStream = Readable.from({
[Symbol.asyncIterator]() {
let count = 0;
return {
async next() {
if (count++ < 3) {
return { value: 'data', done: false }; // ✅ Proper object
}
return { value: undefined, done: true }; // ✅ End of iteration
}
};
}
});Instead of manually creating iterator objects, use generator functions which handle the protocol automatically:
// Manual iterator (error-prone)
function createIterator() {
let i = 0;
return {
next() {
// Easy to forget proper return structure
return { value: i++, done: i > 10 };
}
};
}
// Generator function (safer)
function* createGenerator() {
let i = 0;
while (i <= 10) {
yield i++; // Automatically wrapped in {value, done}
}
}
// Async generator for streams
async function* createAsyncGenerator() {
for (let i = 0; i < 10; i++) {
yield await fetchData(i); // Automatically wrapped
}
}
const stream = Readable.from(createAsyncGenerator());Generators automatically handle the iterator protocol, reducing the chance of returning invalid objects.
Add validation to catch iterator result errors early in development:
function validateIteratorResult(result) {
if (typeof result !== 'object' || result === null) {
throw new TypeError('Iterator result must be an object');
}
if (!('value' in result && 'done' in result)) {
throw new TypeError('Iterator result must have value and done properties');
}
return result;
}
const safeIterator = {
current: 0,
next() {
const result = {
value: this.current++,
done: this.current > 10
};
return validateIteratorResult(result);
}
};For TypeScript, define proper return types:
interface Iterator<T> {
next(): IteratorResult<T>;
}
interface IteratorResult<T> {
value: T;
done: boolean;
}Iterator Protocol Requirements: The JavaScript iterator protocol is strict - the next() method must return an object with 'value' and 'done' properties. While 'value' can be any type (including undefined), the return itself must be an object. Returning undefined, null, or primitives directly violates the protocol.
Async Iterator Differences: Async iterators return Promises that resolve to iterator result objects. The promise itself must resolve to an object - returning a promise that resolves to null or undefined will trigger this error.
Stream Implementation Context: Node.js streams use async iterators internally. When you implement Symbol.asyncIterator or use Readable.from() with a custom iterable, the stream machinery validates iterator results. This error often surfaces during stream consumption rather than creation.
Generator Advantages: Generator functions (function*) and async generator functions (async function*) automatically handle the iterator protocol. When you yield a value, the generator runtime wraps it in {value: yourValue, done: false}. Manual iterator implementations require explicit object returns.
Common Mistake Pattern: A frequent error is conditionally returning different types:
next() {
if (this.hasData) return { value: this.data, done: false };
return null; // ❌ Should return { value: undefined, done: true }
}Debugging Tip: Use console.log() or a debugger to inspect what your next() method actually returns before the error occurs. The issue is always in the return value type, not the logic.
Error: Listener already called (once event already fired)
EventEmitter listener already called with once()
Error: EACCES: permission denied, open '/root/file.txt'
EACCES: permission denied
Error: Invalid encoding specified (stream encoding not supported)
How to fix Invalid encoding error in Node.js readable streams
Error: EINVAL: invalid argument, open
EINVAL: invalid argument, open
TypeError: readableLength must be a positive integer (stream config)
TypeError: readableLength must be a positive integer in Node.js streams