tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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