How to fix TypeError: Asynchronous iterator returned a null reference (promise-based) in Node.js
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.
Quick fix
Fixes TypeError
// ❌ 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 };
}- Ensure next() returns proper iterator result objects
- Wrap library cursors that return null
- Implement Symbol.asyncIterator properly
- Use async generator functions for cleaner code
// ❌ INCORRECT - returns nullasync 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 };}TypeError: Asynchronous iterator returned a null reference (promise-based)
What this error means
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.
Typical symptoms
- TypeError mentioning "asynchronous iterator" and "null reference"
- for await...of loops fail when iterating over custom async iterables
- Code works with synchronous iterators but fails with async versions
- Errors occur when using database cursors or streams with async iteration
- Promise rejection with iterator protocol violation messages
Common causes
- Custom async iterator next() method returns null instead of { done: true } to signal completion
- Using a library cursor (like Mongoose) that returns null rather than following async iterator protocol
- Returning null directly instead of wrapping result in a promise
- Missing or incorrectly implemented [Symbol.asyncIterator]() method
- Attempting to use a non-async-iterable object with for await...of without proper adaptation
How to fix "TypeError: Asynchronous iterator returned a null reference (promise-based)"
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.
Advanced notes
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.
Frequently asked questions
- What does "TypeError" mean?
- 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 }.
- How do I fix "TypeError"?
- 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.
- What causes "TypeError"?
- Custom async iterator next() method returns null instead of { done: true } to signal completion Using a library cursor (like Mongoose) that returns null rather than following async iterator protocol Returning null directly instead of wrapping result in a promise
- Is the "TypeError" error serious?
- It is a medium-severity error — more of a blocker than a risk. The steps above resolve it.
Official resources & further reading
- 📚Node.js Official Documentation
- 📖MDN Symbol.asyncIterator documentation(Official Docs)
- 📖MDN for await...of statement(Official Docs)
- 📝
- 📖MDN AsyncIterator documentation(Official Docs)
- 📝