l, 'options.signal');
  } catch (err) {
    return PromiseReject(err);
  }
  if (typeof ref !== 'boolean') {
    return PromiseReject(
      new ERR_INVALID_ARG_TYPE(
        'options.ref',
        'boolean',
        ref));
  }

  if (signal?.aborted) {
    return PromiseReject(new AbortError(undefined, { cause: signal.reason }));
  }
  let oncancel;
  const ret = new Promise((resolve, reject) => {
    const immediate = new Immediate(resolve, [value]);
    if (!ref) immediate.unref();
    if (signal) {
      oncancel = FunctionPrototypeBind(cancelListenerHandler,
                                       immediate, clearImmediate, reject,
                                       signal);
      kResistStopPropagation ??= require('internal/event_target').kResistStopPropagation;
      signal.addEventListener('abort', oncancel, { __proto__: null, [kResistStopPropagation]: true });
    }
  });
  return oncancel !== undefined ?
    SafePromisePrototypeFinally(
      ret,
      () => signal.removeEventListener('abort', oncancel)) : ret;
}

async function* setInterval(after, value, options = kEmptyObject) {
  validateObject(options, 'options');
  const { signal, ref = true } = options;
  validateAbortSignal(signal, 'options.signal');
  validateBoolean(ref, 'options.ref');

  if (signal?.aborted)
    throw new AbortError(undefined, { cause: signal?.reason });

  let onCancel;
  let interval;
  try {
    let notYielded = 0;
    let callback;
    interval = new Timeout(() => {
      notYielded++;
      if (callback) {
        callback();
        callback = undefined;
      }
    }, after, undefined, true, ref);
    insert(interval, interval._idleTimeout);
    if (signal) {
      onCancel = () => {
        clearInterval(interval);
        if (callback) {
          callback(
            PromiseReject(
              new AbortError(undefined, { cause: signal.reason })));
          callback = undefined;
        }
      };
      kResistStopPropagation ??= require('internal/event_target').kResistStopPropagation;
      signal.addEventListener('abort', onCancel, { __proto__: null, once: true, [kResistStopPropagation]: true });
    }

    while (!signal?.aborted) {
      if (notYielded === 0) {
        await new Promise((resolve) => callback = resolve);
      }
      for (; notYielded > 0; notYielded--) {
        yield value;
      }
    }
    throw new AbortError(undefined, { cause: signal?.reason });
  } finally {
    clearInterval(interval);
    signal?.removeEventListener('abort', onCancel);
  }
}

// TODO(@jasnell): Scheduler is an API currently being discussed by WICG
// for Web Platform standardization: https://github.com/WICG/scheduling-apis
// The scheduler.yield() and scheduler.wait() methods correspond roughly to
// the awaitable setTimeout and setImmediate implementations here. This api
// should be considered to be experimental until the spec for these are
// finalized. Note, also, that Scheduler is expected to be defined as a global,
// but while the API is experimental we shouldn't expose it as such.
class Scheduler {
  constructor() {
    throw new ERR_ILLEGAL_CONSTRUCTOR();
  }

  /**
   * @returns {Promise<void>}
   */
  yield() {
    if (!this[kScheduler])
      throw new ERR_INVALID_THIS('Scheduler');
    return setImmediate();
  }

  /**
   * @typedef {import('../internal/abort_controller').AbortSignal} AbortSignal
   * @param {number} delay
   * @param {{ signal?: AbortSignal }} [options]
   * @returns {Promise<void>}
   */
  wait(delay, options) {
    if (!this[kScheduler])
      throw new ERR_INVALID_THIS('Scheduler');
    return setTimeout(delay, undefined, { signal: options?.signal });
  }
}

module.exports = {
  setTimeout,
  setImmediate,
  setInterval,
  scheduler: ReflectConstruct(function() {
    this[kScheduler] = true;
  }, [], Scheduler),
};
