• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1'use strict';
2
3const {
4  FunctionPrototypeBind,
5  Promise,
6  PromiseReject,
7  ReflectConstruct,
8  SafePromisePrototypeFinally,
9  Symbol,
10} = primordials;
11
12const {
13  Timeout,
14  Immediate,
15  insert,
16} = require('internal/timers');
17const {
18  clearImmediate,
19  clearInterval,
20  clearTimeout,
21} = require('timers');
22
23const {
24  AbortError,
25  codes: {
26    ERR_ILLEGAL_CONSTRUCTOR,
27    ERR_INVALID_ARG_TYPE,
28    ERR_INVALID_THIS,
29  },
30} = require('internal/errors');
31
32const {
33  validateAbortSignal,
34  validateBoolean,
35  validateObject,
36} = require('internal/validators');
37
38const {
39  kEmptyObject,
40} = require('internal/util');
41
42const kScheduler = Symbol('kScheduler');
43
44function cancelListenerHandler(clear, reject, signal) {
45  if (!this._destroyed) {
46    clear(this);
47    reject(new AbortError(undefined, { cause: signal?.reason }));
48  }
49}
50
51function setTimeout(after, value, options = kEmptyObject) {
52  const args = value !== undefined ? [value] : value;
53  if (options == null || typeof options !== 'object') {
54    return PromiseReject(
55      new ERR_INVALID_ARG_TYPE(
56        'options',
57        'Object',
58        options));
59  }
60  const { signal, ref = true } = options;
61  try {
62    validateAbortSignal(signal, 'options.signal');
63  } catch (err) {
64    return PromiseReject(err);
65  }
66  if (typeof ref !== 'boolean') {
67    return PromiseReject(
68      new ERR_INVALID_ARG_TYPE(
69        'options.ref',
70        'boolean',
71        ref));
72  }
73
74  if (signal?.aborted) {
75    return PromiseReject(new AbortError(undefined, { cause: signal.reason }));
76  }
77  let oncancel;
78  const ret = new Promise((resolve, reject) => {
79    const timeout = new Timeout(resolve, after, args, false, ref);
80    insert(timeout, timeout._idleTimeout);
81    if (signal) {
82      oncancel = FunctionPrototypeBind(cancelListenerHandler,
83                                       timeout, clearTimeout, reject, signal);
84      signal.addEventListener('abort', oncancel);
85    }
86  });
87  return oncancel !== undefined ?
88    SafePromisePrototypeFinally(
89      ret,
90      () => signal.removeEventListener('abort', oncancel)) : ret;
91}
92
93function setImmediate(value, options = kEmptyObject) {
94  if (options == null || typeof options !== 'object') {
95    return PromiseReject(
96      new ERR_INVALID_ARG_TYPE(
97        'options',
98        'Object',
99        options));
100  }
101  const { signal, ref = true } = options;
102  try {
103    validateAbortSignal(signal, 'options.signal');
104  } catch (err) {
105    return PromiseReject(err);
106  }
107  if (typeof ref !== 'boolean') {
108    return PromiseReject(
109      new ERR_INVALID_ARG_TYPE(
110        'options.ref',
111        'boolean',
112        ref));
113  }
114
115  if (signal?.aborted) {
116    return PromiseReject(new AbortError(undefined, { cause: signal.reason }));
117  }
118  let oncancel;
119  const ret = new Promise((resolve, reject) => {
120    const immediate = new Immediate(resolve, [value]);
121    if (!ref) immediate.unref();
122    if (signal) {
123      oncancel = FunctionPrototypeBind(cancelListenerHandler,
124                                       immediate, clearImmediate, reject,
125                                       signal);
126      signal.addEventListener('abort', oncancel);
127    }
128  });
129  return oncancel !== undefined ?
130    SafePromisePrototypeFinally(
131      ret,
132      () => signal.removeEventListener('abort', oncancel)) : ret;
133}
134
135async function* setInterval(after, value, options = kEmptyObject) {
136  validateObject(options, 'options');
137  const { signal, ref = true } = options;
138  validateAbortSignal(signal, 'options.signal');
139  validateBoolean(ref, 'options.ref');
140
141  if (signal?.aborted)
142    throw new AbortError(undefined, { cause: signal?.reason });
143
144  let onCancel;
145  let interval;
146  try {
147    let notYielded = 0;
148    let callback;
149    interval = new Timeout(() => {
150      notYielded++;
151      if (callback) {
152        callback();
153        callback = undefined;
154      }
155    }, after, undefined, true, ref);
156    insert(interval, interval._idleTimeout);
157    if (signal) {
158      onCancel = () => {
159        clearInterval(interval);
160        if (callback) {
161          callback(
162            PromiseReject(
163              new AbortError(undefined, { cause: signal.reason })));
164          callback = undefined;
165        }
166      };
167      signal.addEventListener('abort', onCancel, { once: true });
168    }
169
170    while (!signal?.aborted) {
171      if (notYielded === 0) {
172        await new Promise((resolve) => callback = resolve);
173      }
174      for (; notYielded > 0; notYielded--) {
175        yield value;
176      }
177    }
178    throw new AbortError(undefined, { cause: signal?.reason });
179  } finally {
180    clearInterval(interval);
181    signal?.removeEventListener('abort', onCancel);
182  }
183}
184
185// TODO(@jasnell): Scheduler is an API currently being discussed by WICG
186// for Web Platform standardization: https://github.com/WICG/scheduling-apis
187// The scheduler.yield() and scheduler.wait() methods correspond roughly to
188// the awaitable setTimeout and setImmediate implementations here. This api
189// should be considered to be experimental until the spec for these are
190// finalized. Note, also, that Scheduler is expected to be defined as a global,
191// but while the API is experimental we shouldn't expose it as such.
192class Scheduler {
193  constructor() {
194    throw new ERR_ILLEGAL_CONSTRUCTOR();
195  }
196
197  /**
198   * @returns {Promise<void>}
199   */
200  yield() {
201    if (!this[kScheduler])
202      throw new ERR_INVALID_THIS('Scheduler');
203    return setImmediate();
204  }
205
206  /**
207   * @typedef {import('../internal/abort_controller').AbortSignal} AbortSignal
208   * @param {number} delay
209   * @param {{ signal?: AbortSignal }} [options]
210   * @returns {Promise<void>}
211   */
212  wait(delay, options) {
213    if (!this[kScheduler])
214      throw new ERR_INVALID_THIS('Scheduler');
215    return setTimeout(delay, undefined, { signal: options?.signal });
216  }
217}
218
219module.exports = {
220  setTimeout,
221  setImmediate,
222  setInterval,
223  scheduler: ReflectConstruct(function() {
224    this[kScheduler] = true;
225  }, [], Scheduler),
226};
227