255 lines
7.9 KiB
JavaScript
255 lines
7.9 KiB
JavaScript
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.
|
|
//
|
|
// [1]: https://wicg.github.io/observable/#ref-for-abortsignal-add
|
|
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");
|