observable-forEach.any.js (6184B)
1 promise_test(async (t) => { 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 results = []; 10 11 const completion = source.forEach((value) => { 12 results.push(value); 13 }); 14 15 assert_array_equals(results, [1, 2, 3]); 16 await completion; 17 }, "forEach(): Visitor callback called synchronously for each value"); 18 19 promise_test(async (t) => { 20 const error = new Error("error"); 21 const source = new Observable((subscriber) => { 22 throw error; 23 }); 24 25 try { 26 await source.forEach(() => { 27 assert_unreached("Visitor callback is not invoked when Observable errors"); 28 }); 29 assert_unreached("forEach() promise does not resolve when Observable errors"); 30 } catch (e) { 31 assert_equals(e, error); 32 } 33 }, "Errors thrown by Observable reject the returned promise"); 34 35 promise_test(async (t) => { 36 const error = new Error("error"); 37 const source = new Observable((subscriber) => { 38 subscriber.error(error); 39 }); 40 41 try { 42 await source.forEach(() => { 43 assert_unreached("Visitor callback is not invoked when Observable errors"); 44 }); 45 assert_unreached("forEach() promise does not resolve when Observable errors"); 46 } catch (reason) { 47 assert_equals(reason, error); 48 } 49 }, "Errors pushed by Observable reject the returned promise"); 50 51 promise_test(async (t) => { 52 // This will be assigned when `source`'s teardown is called during 53 // unsubscription. 54 let abortReason = null; 55 56 const error = new Error("error"); 57 const source = new Observable((subscriber) => { 58 // Should be called from within the second `next()` call below, when the 59 // `forEach()` visitor callback throws an error, because that triggers 60 // unsubscription from `source`. 61 subscriber.addTeardown(() => abortReason = subscriber.signal.reason); 62 63 subscriber.next(1); 64 subscriber.next(2); 65 subscriber.next(3); 66 subscriber.complete(); 67 }); 68 69 const results = []; 70 71 const completion = source.forEach((value) => { 72 results.push(value); 73 if (value === 2) { 74 throw error; 75 } 76 }); 77 78 assert_array_equals(results, [1, 2]); 79 assert_equals(abortReason, error, 80 "forEach() visitor callback throwing an error triggers unsubscription " + 81 "from the source observable, with the correct abort reason"); 82 83 try { 84 await completion; 85 assert_unreached("forEach() promise does not resolve when visitor throws"); 86 } catch (e) { 87 assert_equals(e, error); 88 } 89 }, "Errors thrown in the visitor callback reject the promise and " + 90 "unsubscribe from the source"); 91 92 // See https://github.com/WICG/observable/issues/96 for discussion about the 93 // timing of Observable AbortSignal `abort` firing and promise rejection. 94 promise_test(async t => { 95 const error = new Error('custom error'); 96 let rejectionError = null; 97 let outerAbortEventMicrotaskRun = false, 98 forEachPromiseRejectionMicrotaskRun = false, 99 innerAbortEventMicrotaskRun = false; 100 101 const source = new Observable(subscriber => { 102 subscriber.signal.addEventListener('abort', () => { 103 queueMicrotask(() => { 104 assert_true(outerAbortEventMicrotaskRun, 105 "Inner abort: outer abort microtask has fired"); 106 assert_true(forEachPromiseRejectionMicrotaskRun, 107 "Inner abort: forEach rejection microtask has fired"); 108 assert_false(innerAbortEventMicrotaskRun, 109 "Inner abort: inner abort microtask has not fired"); 110 111 innerAbortEventMicrotaskRun = true; 112 }); 113 }); 114 }); 115 116 const controller = new AbortController(); 117 controller.signal.addEventListener('abort', () => { 118 queueMicrotask(() => { 119 assert_false(outerAbortEventMicrotaskRun, 120 "Outer abort: outer abort microtask has not fired"); 121 assert_false(forEachPromiseRejectionMicrotaskRun, 122 "Outer abort: forEach rejection microtask has not fired"); 123 assert_false(innerAbortEventMicrotaskRun, 124 "Outer abort: inner abort microtask has not fired"); 125 126 outerAbortEventMicrotaskRun = true; 127 }); 128 }); 129 130 const promise = source.forEach(() => {}, {signal: controller.signal}).catch(e => { 131 rejectionError = e; 132 assert_true(outerAbortEventMicrotaskRun, 133 "Promise rejection: outer abort microtask has fired"); 134 assert_false(forEachPromiseRejectionMicrotaskRun, 135 "Promise rejection: forEach rejection microtask has not fired"); 136 assert_false(innerAbortEventMicrotaskRun, 137 "Promise rejection: inner abort microtask has not fired"); 138 139 forEachPromiseRejectionMicrotaskRun = true; 140 }); 141 142 // This should trigger the following, in this order: 143 // 1. Fire the `abort` event at the outer AbortSignal, whose handler 144 // manually queues a microtask. 145 // 2. Calls "signal abort" on the outer signal's dependent signals. This 146 // queues a microtask to reject the `forEach()` promise. 147 // 3. Fire the `abort` event at the inner AbortSignal, whose handler 148 // manually queues a microtask. 149 controller.abort(error); 150 151 // After a single task, assert that everything has happened correctly (and 152 // incrementally in the right order); 153 await new Promise(resolve => { 154 t.step_timeout(resolve); 155 }); 156 assert_true(outerAbortEventMicrotaskRun, 157 "Final: outer abort microtask has fired"); 158 assert_true(forEachPromiseRejectionMicrotaskRun, 159 "Final: forEach rejection microtask has fired"); 160 assert_true(innerAbortEventMicrotaskRun, 161 "Final: inner abort microtask has fired"); 162 assert_equals(rejectionError, error, "Promise is rejected with the right " + 163 "value"); 164 }, "forEach visitor callback rejection microtask ordering"); 165 166 promise_test(async (t) => { 167 const source = new Observable((subscriber) => { 168 subscriber.next(1); 169 subscriber.next(2); 170 subscriber.next(3); 171 subscriber.complete(); 172 }); 173 174 const results = []; 175 176 const completion = source.forEach((value) => { 177 results.push(value); 178 }); 179 180 assert_array_equals(results, [1, 2, 3]); 181 182 const completionValue = await completion; 183 assert_equals(completionValue, undefined, "Promise resolves with undefined"); 184 }, "forEach() promise resolves with undefined");