observable-finally.any.js (6947B)
1 // Because we test that the global error handler is called at various times. 2 setup({allow_uncaught_exception: true}); 3 4 test(() => { 5 const source = new Observable((subscriber) => { 6 subscriber.next(1); 7 subscriber.next(2); 8 subscriber.next(3); 9 subscriber.complete(); 10 }); 11 12 const results = []; 13 14 source 15 .finally(() => { 16 results.push("finally called"); 17 }) 18 .subscribe({ 19 next: (value) => results.push(value), 20 error: (e) => results.push(e.message), 21 complete: () => results.push("complete"), 22 }); 23 24 assert_array_equals(results, [1, 2, 3, "finally called", "complete"], 25 "finally is called with teardown timing, before complete() is forwarded"); 26 }, "finally(): Mirrors all values and completions from source"); 27 28 test(() => { 29 const source = new Observable((subscriber) => { 30 subscriber.next(1); 31 subscriber.next(2); 32 subscriber.next(3); 33 subscriber.error(new Error("error from source")); 34 }); 35 36 const results = []; 37 38 source 39 .finally(() => { 40 results.push("finally called"); 41 }) 42 .subscribe({ 43 next: (value) => results.push(value), 44 error: (e) => results.push(e.message), 45 complete: () => results.push("complete"), 46 }); 47 48 assert_array_equals(results, [1, 2, 3, "finally called", "error from source"], 49 "finally is called with teardown timing, before complete() is forwarded"); 50 }, "finally(): Mirrors all values and errors from the source"); 51 52 test(() => { 53 const results = []; 54 55 const source = new Observable((subscriber) => { 56 results.push("source subscribe"); 57 subscriber.addTeardown(() => results.push("source teardown")); 58 results.push("source send complete"); 59 subscriber.complete(); 60 }); 61 62 const result = source.finally(() => { 63 results.push("finally handler"); 64 }); 65 66 result.subscribe({ 67 complete: () => results.push("result complete"), 68 }); 69 70 assert_array_equals(results, [ 71 "source subscribe", 72 "source send complete", 73 "source teardown", 74 "finally handler", 75 "result complete", 76 ]); 77 }, "finally(): Callback handler fires BEFORE the source observable completes"); 78 79 test(() => { 80 const results = []; 81 82 const source = new Observable((subscriber) => { 83 results.push("source subscribe"); 84 subscriber.addTeardown(() => results.push("source teardown")); 85 results.push("source send error"); 86 subscriber.error(new Error("error from source")); 87 }); 88 89 const result = source.finally(() => { 90 results.push("finally handler"); 91 }); 92 93 result.subscribe({ 94 error: (e) => results.push(e.message), 95 }); 96 97 assert_array_equals(results, [ 98 "source subscribe", 99 "source send error", 100 "source teardown", 101 "finally handler", 102 "error from source", 103 ]); 104 }, "finally(): Callback handler fires BEFORE the source observable errors"); 105 106 test(() => { 107 const results = []; 108 109 const source = new Observable((subscriber) => { 110 subscriber.complete(); 111 }); 112 113 const result = source 114 .finally(() => { 115 results.push("finally handler 1"); 116 }) 117 .finally(() => { 118 results.push("finally handler 2"); 119 }); 120 121 result.subscribe({ complete: () => results.push("result complete") }); 122 123 assert_array_equals(results, 124 ["finally handler 1", "finally handler 2", "result complete"]); 125 }, "finally(): Handlers run in composition order"); 126 127 test(() => { 128 const source = new Observable(subscriber => { 129 subscriber.error("producer error"); 130 }); 131 132 const results = []; 133 134 self.addEventListener('error', e => results.push(e.error.message), {once: true}); 135 136 source 137 .finally(() => { 138 throw new Error("error from finally"); 139 }) 140 .subscribe({ 141 next: () => results.push("next"), 142 error: (e) => results.push(e), 143 complete: () => results.push("complete"), 144 }); 145 146 assert_array_equals(results, ["error from finally", "producer error"]); 147 }, "finally(): Errors thrown in the finally handler " + 148 "(during Subscriber#error()) are reported to the global immediately"); 149 150 test(() => { 151 const source = new Observable((subscriber) => { 152 subscriber.complete(); 153 }); 154 155 const results = []; 156 157 self.addEventListener('error', e => results.push(e.error.message), {once: true}); 158 159 source 160 .finally(() => { 161 throw new Error("error from finally"); 162 }) 163 .subscribe({ 164 next: () => results.push("next"), 165 error: (e) => results.push("unreached"), 166 complete: () => results.push("complete"), 167 }); 168 169 assert_array_equals(results, ["error from finally", "complete"]); 170 }, "finally(): Errors thrown in the finally handler " + 171 "(during Subscriber#complete()) are reported to the global immediately"); 172 173 test(() => { 174 const results = []; 175 176 const source = new Observable((subscriber) => { 177 subscriber.addTeardown(() => results.push("source teardown")); 178 }); 179 180 const controller = new AbortController(); 181 182 source 183 .finally(() => results.push("downstream finally handler")) 184 .subscribe({}, { signal: controller.signal }); 185 186 controller.abort(); 187 188 assert_array_equals(results, ["source teardown", "downstream finally handler"]); 189 }, "finally(): Callback is run if consumer aborts the subscription"); 190 191 test(() => { 192 const results = []; 193 const result = new Observable((subscriber) => { 194 subscriber.next(1); 195 subscriber.next(2); 196 subscriber.complete(); 197 }).flatMap((value) => { 198 results.push(`flatMap ${value}`); 199 return new Observable((subscriber) => { 200 subscriber.next(value); 201 subscriber.next(value); 202 subscriber.next(value); 203 subscriber.complete(); 204 }).finally(() => { 205 results.push(`finally ${value}`); 206 }); 207 }); 208 209 result.subscribe({ 210 next: (value) => results.push(`result ${value}`), 211 complete: () => results.push("result complete"), 212 }); 213 214 assert_array_equals(results, [ 215 "flatMap 1", 216 "result 1", 217 "result 1", 218 "result 1", 219 "finally 1", 220 "flatMap 2", 221 "result 2", 222 "result 2", 223 "result 2", 224 "finally 2", 225 "result complete", 226 ]); 227 }, "finally(): Callback is run before next inner subscription in flatMap()"); 228 229 test(() => { 230 const results = []; 231 const result = new Observable((subscriber) => { 232 subscriber.next(1); 233 subscriber.next(2); 234 subscriber.complete(); 235 }).switchMap((value) => { 236 results.push(`switchMap ${value}`); 237 return new Observable((subscriber) => { 238 subscriber.next(value); 239 subscriber.next(value); 240 subscriber.next(value); 241 subscriber.complete(); 242 }).finally(() => { 243 results.push(`finally ${value}`); 244 }); 245 }); 246 247 result.subscribe({ 248 next: (value) => results.push(`result ${value}`), 249 complete: () => results.push("result complete"), 250 }); 251 252 assert_array_equals(results, [ 253 "switchMap 1", 254 "result 1", 255 "result 1", 256 "result 1", 257 "finally 1", 258 "switchMap 2", 259 "result 2", 260 "result 2", 261 "result 2", 262 "finally 2", 263 "result complete", 264 ]); 265 }, "finally(): Callback is run before next inner subscription in switchMap()");