Source code

Revision control

Copy as Markdown

Other Tools

Test Info: Warnings

test(() => {
const source = new Observable(subscriber => {
subscriber.next(1);
subscriber.next(2);
subscriber.next(3);
subscriber.complete();
});
const caughtObservable = source.catch(() => {
assert_unreached("catch() is not called");
});
const results = [];
caughtObservable.subscribe({
next: value => results.push(value),
complete: () => results.push('complete')
});
assert_array_equals(results, [1, 2, 3, 'complete']);
}, "catch(): Returns an Observable that is a pass-through for next()/complete()");
test(() => {
let sourceError = new Error("from the source");
const source = new Observable(subscriber => {
subscriber.next(1);
subscriber.next(2);
subscriber.error(sourceError);
});
const caughtObservable = source.catch(error => {
assert_equals(error, sourceError);
return new Observable(subscriber => {
subscriber.next(3);
subscriber.complete();
});
});
const results = [];
caughtObservable.subscribe({
next: value => results.push(value),
complete: () => results.push("complete"),
});
assert_array_equals(results, [1, 2, 3, 'complete']);
}, "catch(): Handle errors from source and flatten to a new Observable");
test(() => {
const sourceError = new Error("from the source");
const source = new Observable(subscriber => {
subscriber.next(1);
subscriber.next(2);
subscriber.error(sourceError);
});
const catchCallbackError = new Error("from the catch callback");
const caughtObservable = source.catch(error => {
assert_equals(error, sourceError);
throw catchCallbackError;
});
const results = [];
caughtObservable.subscribe({
next: value => results.push(value),
error: error => {
results.push(error);
},
complete: () => results.push('complete'),
});
assert_array_equals(results, [1, 2, catchCallbackError]);
}, "catch(): Errors thrown in the catch() callback are sent to the consumer's error handler");
test(() => {
// A common use case is logging and keeping the stream alive.
const source = new Observable(subscriber => {
subscriber.next(1);
subscriber.next(2);
subscriber.next(3);
subscriber.complete();
});
const flatteningError = new Error("from the flattening operation");
function errorsOnTwo(value) {
return new Observable(subscriber => {
if (value === 2) {
subscriber.error(flatteningError);
} else {
subscriber.next(value);
subscriber.complete();
}
});
}
const results = [];
source.flatMap(value => errorsOnTwo(value)
.catch(error => {
results.push(error);
// This empty array converts to an Observable which automatically
// completes.
return [];
})
).subscribe({
next: value => results.push(value),
complete: () => results.push("complete")
});
assert_array_equals(results, [1, flatteningError, 3, "complete"]);
}, "catch(): CatchHandler can return an empty iterable");
promise_test(async () => {
const sourceError = new Error("from the source");
const source = new Observable(subscriber => {
subscriber.next(1);
subscriber.next(2);
subscriber.error(sourceError);
});
const caughtObservable = source.catch(error => {
assert_equals(error, sourceError);
return Promise.resolve(error.message);
});
const results = await caughtObservable.toArray();
assert_array_equals(results, [1, 2, "from the source"]);
}, "catch(): CatchHandler can return a Promise");
promise_test(async () => {
const source = new Observable(subscriber => {
subscriber.next(1);
subscriber.next(2);
subscriber.error(new Error('from the source'));
});
const caughtObservable = source.catch(async function* (error) {
assert_true(error instanceof Error);
assert_equals(error.message, 'from the source');
yield 3;
});
const results = await caughtObservable.toArray();
assert_array_equals(results, [1, 2, 3], 'catch(): should handle returning an observable');
}, 'catch(): should handle returning an async iterable');
test(() => {
const sourceError = new Error("from the source");
const source = new Observable(subscriber => {
subscriber.next(1);
subscriber.next(2);
subscriber.error(sourceError);
});
const caughtObservable = source.catch(error => {
assert_equals(error, sourceError);
// Primitive values like this are not convertible to an Observable, via the
// `from()` semantics.
return 3;
});
const results = [];
caughtObservable.subscribe({
next: value => results.push(value),
error: error => {
assert_true(error instanceof TypeError);
results.push("TypeError");
},
complete: () => results.push("complete"),
});
assert_array_equals(results, [1, 2, "TypeError"]);
}, "catch(): CatchHandler emits an error if the value returned is not " +
"convertible to an Observable");
test(() => {
const source = new Observable(subscriber => {
susbcriber.error(new Error("from the source"));
});
const results = [];
const innerSubscriptionError = new Error("CatchHandler subscription error");
const catchObservable = source.catch(() => {
results.push('CatchHandler invoked');
return new Observable(subscriber => {
throw innerSubscriptionError;
});
});
catchObservable.subscribe({
error: e => {
results.push(e);
}
});
assert_array_equals(results, ['CatchHandler invoked', innerSubscriptionError]);
}, "catch(): CatchHandler returns an Observable that throws immediately on " +
"subscription");
// This test asserts that the relationship between (a) the AbortSignal passed
// into `subscribe()` and (b) the AbortSignal associated with the Observable
// returned from `catch()`'s CatchHandler is not a "dependent" relationship.
// This is important because Observables have moved away from the "dependent
// abort signal" infrastructure in https://github.com/WICG/observable/pull/154,
// and this test asserts so.
//
// Here are all of the associated Observables and signals in this test:
// 1. Raw outer signal passed into `subscribe()`
// 2. catchObservable's inner Subscriber's signal
// a. Per the above PR, and Subscriber's initialization logic [1], this
// signal is set to abort in response to (1)'s abort algorithms. This
// means its "abort" event gets fired before (1)'s.
// 3. Inner CatchHandler-returned Observable's Subscriber's signal
// a. Also per [1], this is set to abort in response to (2)'s abort
// algorithms, since we subscribe to this "inner Observable" with (2)'s
// signal as the `SubscribeOptions#signal`.
//
// (1), (2), and (3) above all form an abort chain:
// (1) --> (2) --> (3)
//
// …such that when (1) aborts, its abort algorithms immediately abort (2),
// whose abort algorithms immediately abort (3). Finally on the way back up the
// chain, (3)'s `abort` event is fired, (2)'s `abort` event is fired, and then
// (1)'s `abort` event is fired. This ordering of abort events is what this test
// ensures.
//
test(() => {
const results = [];
const source = new Observable(subscriber =>
susbcriber.error(new Error("from the source")));
const catchObservable = source.catch(() => {
return new Observable(subscriber => {
subscriber.addTeardown(() => results.push('inner teardown'));
subscriber.signal.addEventListener('abort',
e => results.push('inner signal abort'));
// No values or completion. We'll just wait for the subscriber to abort
// its subscription.
});
});
const ac = new AbortController();
ac.signal.addEventListener('abort', e => results.push('outer signal abort'));
catchObservable.subscribe({}, {signal: ac.signal});
ac.abort();
assert_array_equals(results, ['inner signal abort', 'inner teardown', 'outer signal abort']);
}, "catch(): Abort order between outer AbortSignal and inner CatchHandler subscriber's AbortSignal");