{"record":{"id":"f782ba9a0cfa5c4e","repo":"ReactiveX/rxjs","slug":"iterator-return-must-return-an-object","errorCode":null,"errorMessage":"Iterator return() must return an Object","messagePattern":"Iterator return\\(\\) must return an Object","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"packages/observable-polyfill/src/index.ts","lineNumber":385,"sourceCode":"    const iterator = asyncIteratorMethod.call(value);\n    if (!isObject(iterator)) {\n      throw new TypeError('Symbol.asyncIterator must return an object');\n    }\n    return { iterator };\n  }\n\n  return { iterator: getSyncIteratorRecord(value).iterator };\n}\n\nfunction closeSyncIterator(record: SyncIteratorRecord<unknown>, reason: unknown): void {\n  const returnMethod = getMethod(record.iterator, 'return');\n  if (!returnMethod) {\n    return;\n  }\n\n  const result = returnMethod.call(record.iterator, reason);\n  if (!isObject(result)) {\n    throw new TypeError('Iterator return() must return an Object');\n  }\n}\n\nfunction closeAsyncIterator(record: AsyncIteratorRecord, reason: unknown): void {\n  let result: unknown;\n  try {\n    const returnMethod = getMethod(record.iterator, 'return');\n    if (!returnMethod) {\n      return;\n    }\n    result = returnMethod.call(record.iterator, reason);\n  } catch (error) {\n    globalThis.queueMicrotask(() => reportUnhandledRejection(error));\n    return;\n  }\n\n  void Promise.resolve(result).then(\n    (returnResult) => {","sourceCodeStart":367,"sourceCodeEnd":403,"githubUrl":"https://github.com/ReactiveX/rxjs/blob/54796b38a57e6309f9861e174737479bb3f63f61/packages/observable-polyfill/src/index.ts#L367-L403","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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."],"exampleFix":"// before\nconst it = {\n  next: () => ({ done: false, value: 1 }),\n  return() { cleanup(); } // returns undefined\n};\n// after\nconst it = {\n  next: () => ({ done: false, value: 1 }),\n  return() { cleanup(); return { done: true }; }\n};","handlingStrategy":"validation","validationCode":"const it = iterable[Symbol.iterator]();\nif (typeof (it as any).return === 'function') {\n  const probe = { done: true } as const; // only test shape on a throwaway iterator in tests\n}","typeGuard":"function hasValidReturn(it: Iterator<unknown>): boolean {\n  return typeof (it as any).return !== 'function'; // absence is safe; presence must return objects\n}","tryCatchPattern":"subscriber.addTeardown(() => { try { iterator.return?.(reason); } catch { /* ignore teardown errors */ } });","preventionTips":["End every custom return() with `return { done: true };`.","Prefer generators — their return() is protocol-correct.","Test the unsubscribe path of iterable-backed observables."],"tags":["iterator","iterator-return","teardown","unsubscribe","typeerror"],"backgroundTag":"invalid-iterator-protocol","analyzedSha":"54796b38a57e6309f9861e174737479bb3f63f61","analyzedAt":"2026-08-28T10:21:27.410Z","schemaVersion":2},"datasetVersion":"2026-08-28T11:17:15.048Z"}