This error occurs when an async iterator's next() method returns null instead of a proper iterator result object. Async iterators must return promises that resolve to objects with done and value properties.
This error indicates a violation of the async iterator protocol in Node.js. When implementing an async iterator, the next() method must always return a promise that resolves to an object with two properties: done (a boolean) and value (the current item or undefined when complete). The error typically occurs when custom async iterator implementations incorrectly return null to signal completion, rather than following the proper convention of returning { done: true }. This breaks the contract that for await...of loops and other async iteration consumers expect. Some libraries, particularly database cursors like Mongoose, return null instead of proper iterator result objects. When trying to use these with for await...of or other async iteration patterns without proper adaptation, this error occurs.
Verify your async iterator's next() method returns an object with done and value properties, not null:
// ❌ INCORRECT - returns null
async next() {
if (this.index >= this.items.length) {
return null; // This causes the error
}
return { value: this.items[this.index++], done: false };
}
// ✅ CORRECT - returns { done: true }
async next() {
if (this.index >= this.items.length) {
return { value: undefined, done: true };
}
return { value: this.items[this.index++], done: false };
}The done property signals completion, not a null return value.
If you're using a library like Mongoose that returns null instead of proper iterator results, wrap it in an async iterable:
// Wrap Mongoose cursor for async iteration
async function* makeAsyncIterable(cursor) {
let doc;
while ((doc = await cursor.next()) !== null) {
yield doc;
}
}
// Usage
const cursor = MyModel.find().cursor();
for await (const doc of makeAsyncIterable(cursor)) {
console.log(doc);
}This adapter converts null-based completion to the proper { done: true } pattern.
Ensure your object implements the async iterator protocol correctly:
class AsyncIterable {
constructor(items) {
this.items = items;
}
[Symbol.asyncIterator]() {
let index = 0;
const items = this.items;
return {
async next() {
if (index < items.length) {
return { value: items[index++], done: false };
}
return { value: undefined, done: true };
}
};
}
}
// Usage
const iterable = new AsyncIterable([1, 2, 3]);
for await (const value of iterable) {
console.log(value); // 1, 2, 3
}The [Symbol.asyncIterator]() method returns an object with a next() method that follows the protocol.
Instead of manually implementing the async iterator protocol, use async generator functions:
// Manual implementation (verbose)
class AsyncRange {
constructor(start, end) {
this.start = start;
this.end = end;
}
[Symbol.asyncIterator]() {
let current = this.start;
const end = this.end;
return {
async next() {
if (current <= end) {
return { value: current++, done: false };
}
return { value: undefined, done: true };
}
};
}
}
// Async generator (cleaner, handles protocol automatically)
async function* asyncRange(start, end) {
for (let i = start; i <= end; i++) {
await new Promise(resolve => setTimeout(resolve, 10));
yield i;
}
}
// Both work the same way
for await (const num of new AsyncRange(1, 5)) {
console.log(num);
}
for await (const num of asyncRange(1, 5)) {
console.log(num);
}Async generators automatically handle the iterator protocol correctly.
Ensure your next() method returns a promise, even for synchronous completions:
// ❌ INCORRECT - returns plain object
next() {
return { value: this.data, done: true };
}
// ✅ CORRECT - returns promise
async next() {
return { value: this.data, done: true };
}
// ✅ ALSO CORRECT - explicit promise
next() {
return Promise.resolve({ value: this.data, done: true });
}The async iterator protocol requires promises, not plain objects.
Node.js Version Support: Async iterators and for await...of have been natively supported since Node.js 10.x. No polyfills are needed for modern Node versions.
Iterator vs Iterable: An async iterator is an object with a next() method, while an async iterable is an object with a [Symbol.asyncIterator]() method that returns an async iterator. For for await...of to work, you need an async iterable.
Return and Throw Methods: Complete async iterator implementations can also include return() and throw() methods for cleanup and error handling. These methods must also return promises resolving to iterator result objects:
[Symbol.asyncIterator]() {
return {
async next() { /* ... */ },
async return(value) {
// Cleanup logic
return { value, done: true };
},
async throw(error) {
// Error handling
throw error;
}
};
}Streams Integration: Node.js streams implement the async iterable protocol in recent versions, allowing direct use with for await...of without manual wrapping.
Performance Considerations: Async iteration has overhead compared to synchronous iteration. For large datasets where async operations aren't necessary, consider using synchronous iterators or direct array processing.
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