Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions docs/rxjs-next/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,11 @@ iterators use explicit protocol loops so the fallback can preserve iterator
method sampling, `return(reason)`, abort timing, and pending-result behavior
that `for await...of` intentionally hides.

For subclasses that supply their own Subscriber implementation, promise
rejections use the public `subscriber.error()` method. Polyfill Subscribers
retain the internal error-reporting path that omits source-location information
for promise rejections.

The structure and behavior pass the pinned Observable WPT revision in window,
dedicated-worker, same-origin iframe, and Web IDL coverage. D-045 supersedes
D-042: the fallback again enforces the pinned revision's required-argument rule
Expand Down
18 changes: 18 additions & 0 deletions docs/rxjs-next/PROJECT_PLAN.md
Original file line number Diff line number Diff line change
Expand Up @@ -3718,6 +3718,7 @@ conformance implementation depends on a runnable harness.
remains the sole `NEXT` item until the command publishes and verifies
`9.0.0-beta.0`, package access disallows automation tokens, and the immutable
GitHub Release is recorded.

### 2026-08-05 — P6.11 callback receiver removal

- Audited the complete RxJS production source and removed callback `thisArg`
Expand All @@ -3734,3 +3735,20 @@ conformance implementation depends on a runnable harness.
the release performance floor.
- Recorded D-059, marked P6.11 `DONE`, and retained P6.10 as the sole `NEXT`
item.

### 2026-09-16 — ColdObservable promise rejection fix

- At the user's request, fixed `ColdObservable.from(promise)` rejection
delivery. Subclass-provided Subscribers use their public `error()` method;
polyfill Subscribers retain the existing source-location reporting behavior.
- Added regression coverage for multiple direct subscribers, a missing error
handler, and cancellation before rejection delivery. The original code
failed two assertions and produced four unhandled rejections; the fix passes.
- Validation passed: 753 RxJS source tests, 52 polyfill tests, both package
builds and public type checks, and both lints (existing warnings only).
The original reported reproduction now delivers the error without an
unhandled rejection.
- Pinned WPT validation passed all 52 URLs, 525 upstream subtests, and 52 RxJS
identity attestations at WPT `6a009d73f0d315941b90cac13a9523a2a08c631b`
in Chrome `150.0.7871.126`.
- P6.10 remains the sole `NEXT` item.
8 changes: 7 additions & 1 deletion packages/observable-polyfill/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -569,7 +569,13 @@ class ObservableImpl<T> implements Subscribable<T> {
subscriber.next(output);
subscriber.complete();
},
(error) => (subscriber as Subscriber<T>)[errorSubscriber](error, false)
(error) => {
if (subscriber instanceof Subscriber) {
subscriber[errorSubscriber](error, false);
} else {
subscriber.error(error);
}
}
);
});
}
Expand Down
41 changes: 41 additions & 0 deletions packages/rxjs/src/cold-observable.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,47 @@ describe('ColdObservable', () => {
expect(reported).toEqual([expected]);
});

it('delivers promise rejections to every direct subscriber', async () => {
const expected = new Error('promise failed');
const results: unknown[] = [];
const source = ColdObservable.from(Promise.reject(expected));
const observer = {
next: (value: unknown) => results.push(value),
error: (error: unknown) => results.push(error),
complete: () => results.push('complete'),
};

expect(source).toBeInstanceOf(ColdObservable);
source.subscribe(observer);
source.subscribe(observer);
await Promise.resolve();

expect(results).toEqual([expected, expected]);
expect(reported).toEqual([]);
});

it('reports promise rejections when the observer has no error handler', async () => {
const expected = new Error('promise failed');

ColdObservable.from(Promise.reject(expected)).subscribe();
await Promise.resolve();

expect(reported).toEqual([expected]);
});

it('ignores promise rejections after the observer aborts', async () => {
const expected = new Error('promise failed');
const errors: unknown[] = [];
const controller = new AbortController();

ColdObservable.from(Promise.reject(expected)).subscribe({ error: (error) => errors.push(error) }, { signal: controller.signal });
controller.abort();
await Promise.resolve();

expect(errors).toEqual([]);
expect(reported).toEqual([]);
});

it('is a platform Observable subclass', () => {
const source = new ColdObservable<number>(() => {});

Expand Down