observable-catch.any.js (8046B)
1 test(() => { 2 const source = new Observable(subscriber => { 3 subscriber.next(1); 4 subscriber.next(2); 5 subscriber.next(3); 6 subscriber.complete(); 7 }); 8 9 const caughtObservable = source.catch(() => { 10 assert_unreached("catch() is not called"); 11 }); 12 13 const results = []; 14 15 caughtObservable.subscribe({ 16 next: value => results.push(value), 17 complete: () => results.push('complete') 18 }); 19 20 assert_array_equals(results, [1, 2, 3, 'complete']); 21 }, "catch(): Returns an Observable that is a pass-through for next()/complete()"); 22 23 test(() => { 24 let sourceError = new Error("from the source"); 25 const source = new Observable(subscriber => { 26 subscriber.next(1); 27 subscriber.next(2); 28 subscriber.error(sourceError); 29 }); 30 31 const caughtObservable = source.catch(error => { 32 assert_equals(error, sourceError); 33 return new Observable(subscriber => { 34 subscriber.next(3); 35 subscriber.complete(); 36 }); 37 }); 38 39 const results = []; 40 41 caughtObservable.subscribe({ 42 next: value => results.push(value), 43 complete: () => results.push("complete"), 44 }); 45 46 assert_array_equals(results, [1, 2, 3, 'complete']); 47 }, "catch(): Handle errors from source and flatten to a new Observable"); 48 49 test(() => { 50 const sourceError = new Error("from the source"); 51 const source = new Observable(subscriber => { 52 subscriber.next(1); 53 subscriber.next(2); 54 subscriber.error(sourceError); 55 }); 56 57 const catchCallbackError = new Error("from the catch callback"); 58 const caughtObservable = source.catch(error => { 59 assert_equals(error, sourceError); 60 throw catchCallbackError; 61 }); 62 63 const results = []; 64 65 caughtObservable.subscribe({ 66 next: value => results.push(value), 67 error: error => { 68 results.push(error); 69 }, 70 complete: () => results.push('complete'), 71 }); 72 73 assert_array_equals(results, [1, 2, catchCallbackError]); 74 }, "catch(): Errors thrown in the catch() callback are sent to the consumer's error handler"); 75 76 test(() => { 77 // A common use case is logging and keeping the stream alive. 78 const source = new Observable(subscriber => { 79 subscriber.next(1); 80 subscriber.next(2); 81 subscriber.next(3); 82 subscriber.complete(); 83 }); 84 85 const flatteningError = new Error("from the flattening operation"); 86 function errorsOnTwo(value) { 87 return new Observable(subscriber => { 88 if (value === 2) { 89 subscriber.error(flatteningError); 90 } else { 91 subscriber.next(value); 92 subscriber.complete(); 93 } 94 }); 95 } 96 97 const results = []; 98 99 source.flatMap(value => errorsOnTwo(value) 100 .catch(error => { 101 results.push(error); 102 // This empty array converts to an Observable which automatically 103 // completes. 104 return []; 105 }) 106 ).subscribe({ 107 next: value => results.push(value), 108 complete: () => results.push("complete") 109 }); 110 111 assert_array_equals(results, [1, flatteningError, 3, "complete"]); 112 }, "catch(): CatchHandler can return an empty iterable"); 113 114 promise_test(async () => { 115 const sourceError = new Error("from the source"); 116 const source = new Observable(subscriber => { 117 subscriber.next(1); 118 subscriber.next(2); 119 subscriber.error(sourceError); 120 }); 121 122 const caughtObservable = source.catch(error => { 123 assert_equals(error, sourceError); 124 return Promise.resolve(error.message); 125 }); 126 127 const results = await caughtObservable.toArray(); 128 129 assert_array_equals(results, [1, 2, "from the source"]); 130 }, "catch(): CatchHandler can return a Promise"); 131 132 promise_test(async () => { 133 const source = new Observable(subscriber => { 134 subscriber.next(1); 135 subscriber.next(2); 136 subscriber.error(new Error('from the source')); 137 }); 138 139 const caughtObservable = source.catch(async function* (error) { 140 assert_true(error instanceof Error); 141 assert_equals(error.message, 'from the source'); 142 yield 3; 143 }); 144 145 const results = await caughtObservable.toArray(); 146 147 assert_array_equals(results, [1, 2, 3], 'catch(): should handle returning an observable'); 148 }, 'catch(): should handle returning an async iterable'); 149 150 test(() => { 151 const sourceError = new Error("from the source"); 152 const source = new Observable(subscriber => { 153 subscriber.next(1); 154 subscriber.next(2); 155 subscriber.error(sourceError); 156 }); 157 158 const caughtObservable = source.catch(error => { 159 assert_equals(error, sourceError); 160 // Primitive values like this are not convertible to an Observable, via the 161 // `from()` semantics. 162 return 3; 163 }); 164 165 const results = []; 166 167 caughtObservable.subscribe({ 168 next: value => results.push(value), 169 error: error => { 170 assert_true(error instanceof TypeError); 171 results.push("TypeError"); 172 }, 173 complete: () => results.push("complete"), 174 }); 175 176 assert_array_equals(results, [1, 2, "TypeError"]); 177 }, "catch(): CatchHandler emits an error if the value returned is not " + 178 "convertible to an Observable"); 179 180 test(() => { 181 const source = new Observable(subscriber => { 182 susbcriber.error(new Error("from the source")); 183 }); 184 185 const results = []; 186 187 const innerSubscriptionError = new Error("CatchHandler subscription error"); 188 const catchObservable = source.catch(() => { 189 results.push('CatchHandler invoked'); 190 return new Observable(subscriber => { 191 throw innerSubscriptionError; 192 }); 193 }); 194 195 catchObservable.subscribe({ 196 error: e => { 197 results.push(e); 198 } 199 }); 200 201 assert_array_equals(results, ['CatchHandler invoked', innerSubscriptionError]); 202 }, "catch(): CatchHandler returns an Observable that throws immediately on " + 203 "subscription"); 204 205 // This test asserts that the relationship between (a) the AbortSignal passed 206 // into `subscribe()` and (b) the AbortSignal associated with the Observable 207 // returned from `catch()`'s CatchHandler is not a "dependent" relationship. 208 // This is important because Observables have moved away from the "dependent 209 // abort signal" infrastructure in https://github.com/WICG/observable/pull/154, 210 // and this test asserts so. 211 // 212 // Here are all of the associated Observables and signals in this test: 213 // 1. Raw outer signal passed into `subscribe()` 214 // 2. catchObservable's inner Subscriber's signal 215 // a. Per the above PR, and Subscriber's initialization logic [1], this 216 // signal is set to abort in response to (1)'s abort algorithms. This 217 // means its "abort" event gets fired before (1)'s. 218 // 3. Inner CatchHandler-returned Observable's Subscriber's signal 219 // a. Also per [1], this is set to abort in response to (2)'s abort 220 // algorithms, since we subscribe to this "inner Observable" with (2)'s 221 // signal as the `SubscribeOptions#signal`. 222 // 223 // (1), (2), and (3) above all form an abort chain: 224 // (1) --> (2) --> (3) 225 // 226 // …such that when (1) aborts, its abort algorithms immediately abort (2), 227 // whose abort algorithms immediately abort (3). Finally on the way back up the 228 // chain, (3)'s `abort` event is fired, (2)'s `abort` event is fired, and then 229 // (1)'s `abort` event is fired. This ordering of abort events is what this test 230 // ensures. 231 // 232 // [1]: https://wicg.github.io/observable/#ref-for-abortsignal-add 233 test(() => { 234 const results = []; 235 const source = new Observable(subscriber => 236 susbcriber.error(new Error("from the source"))); 237 238 const catchObservable = source.catch(() => { 239 return new Observable(subscriber => { 240 subscriber.addTeardown(() => results.push('inner teardown')); 241 subscriber.signal.addEventListener('abort', 242 e => results.push('inner signal abort')); 243 244 // No values or completion. We'll just wait for the subscriber to abort 245 // its subscription. 246 }); 247 }); 248 249 const ac = new AbortController(); 250 ac.signal.addEventListener('abort', e => results.push('outer signal abort')); 251 catchObservable.subscribe({}, {signal: ac.signal}); 252 ac.abort(); 253 254 assert_array_equals(results, ['inner signal abort', 'inner teardown', 'outer signal abort']); 255 }, "catch(): Abort order between outer AbortSignal and inner CatchHandler subscriber's AbortSignal");