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