ReactiveX/rxjs · error · TypeError
Iterator return() must return an Object
Error message
Iterator return() must return an Object
What it means
During teardown/cancellation, the polyfill closes a sync iterator by calling its optional return() method (closeSyncIterator → closeIterator). The iterator protocol requires return() to respond with an object; if it returns a primitive or undefined, this TypeError is thrown while unsubscribing from an Observable created from an iterable.
Source
Thrown at packages/observable-polyfill/src/index.ts:385
const iterator = asyncIteratorMethod.call(value);
if (!isObject(iterator)) {
throw new TypeError('Symbol.asyncIterator must return an object');
}
return { iterator };
}
return { iterator: getSyncIteratorRecord(value).iterator };
}
function closeSyncIterator(record: SyncIteratorRecord<unknown>, reason: unknown): void {
const returnMethod = getMethod(record.iterator, 'return');
if (!returnMethod) {
return;
}
const result = returnMethod.call(record.iterator, reason);
if (!isObject(result)) {
throw new TypeError('Iterator return() must return an Object');
}
}
function closeAsyncIterator(record: AsyncIteratorRecord, reason: unknown): void {
let result: unknown;
try {
const returnMethod = getMethod(record.iterator, 'return');
if (!returnMethod) {
return;
}
result = returnMethod.call(record.iterator, reason);
} catch (error) {
globalThis.queueMicrotask(() => reportUnhandledRejection(error));
return;
}
void Promise.resolve(result).then(
(returnResult) => {View on GitHub (pinned to 54796b38a5)
Solutions
- In the iterator's return(reason) method, end with return { done: true }; (returning any object).
- If delegating: return inner.return?.() ?? { done: true };
- Remove return() entirely if no cleanup is needed — its absence is legal.
- Write a test that subscribes then unsubscribes mid-iteration to exercise the return() path.
Example fix
// before
const it = {
next: () => ({ done: false, value: 1 }),
return() { cleanup(); } // returns undefined
};
// after
const it = {
next: () => ({ done: false, value: 1 }),
return() { cleanup(); return { done: true }; }
}; Defensive patterns
Strategy: validation
Validate before calling
const it = iterable[Symbol.iterator]();
if (typeof (it as any).return === 'function') {
const probe = { done: true } as const; // only test shape on a throwaway iterator in tests
} Type guard
function hasValidReturn(it: Iterator<unknown>): boolean {
return typeof (it as any).return !== 'function'; // absence is safe; presence must return objects
} Try / catch
subscriber.addTeardown(() => { try { iterator.return?.(reason); } catch { /* ignore teardown errors */ } }); Prevention
- End every custom return() with `return { done: true };`.
- Prefer generators — their return() is protocol-correct.
- Test the unsubscribe path of iterable-backed observables.
When it happens
Trigger: Unsubscribing (or an error/early completion causing teardown) from Observable.from(iterable) where the iterable's iterator defines a return() that returns a non-object (commonly undefined).
Common situations: Hand-written iterators implementing return() for cleanup but forgetting to return an object; partially-implemented iterator mocks in tests; delegating iterators whose return() forwards a void cleanup function's result.
Related errors
- Iterator must define a callable next() method
- Iterator next() must return an Object
- Symbol.asyncIterator must return an object
- ${String(value)} is not observable
- Observable constructor requires a callback
AI-assisted analysis of ReactiveX/rxjs@54796b38a5 (2026-08-28).
Data as JSON: /api/errors/f782ba9a0cfa5c4e.
Report an issue: GitHub.