| 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"); |