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