Skip to content

Commit 9660a78

Browse files
jasnelltargos
authored andcommitted
timers: move promisified timers implementations
Move the promisified timers implementations into a new internal. submodule. Also adds `ref` option to the promisified versions. Signed-off-by: James M Snell <[email protected]> PR-URL: #33950 Backport-PR-URL: #38386 Reviewed-By: Denys Otrishko <[email protected]> Reviewed-By: Benjamin Gruenbaum <[email protected]>
1 parent 59a8425 commit 9660a78

File tree

5 files changed

+194
-128
lines changed

5 files changed

+194
-128
lines changed

lib/internal/timers.js

+43-1
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,8 @@ const {
8484
scheduleTimer,
8585
toggleTimerRef,
8686
getLibuvNow,
87-
immediateInfo
87+
immediateInfo,
88+
toggleImmediateRef
8889
} = internalBinding('timers');
8990

9091
const {
@@ -593,12 +594,53 @@ function getTimerCallbacks(runNextTicks) {
593594
};
594595
}
595596

597+
class Immediate {
598+
constructor(callback, args) {
599+
this._idleNext = null;
600+
this._idlePrev = null;
601+
this._onImmediate = callback;
602+
this._argv = args;
603+
this._destroyed = false;
604+
this[kRefed] = false;
605+
606+
initAsyncResource(this, 'Immediate');
607+
608+
this.ref();
609+
immediateInfo[kCount]++;
610+
611+
immediateQueue.append(this);
612+
}
613+
614+
ref() {
615+
if (this[kRefed] === false) {
616+
this[kRefed] = true;
617+
if (immediateInfo[kRefCount]++ === 0)
618+
toggleImmediateRef(true);
619+
}
620+
return this;
621+
}
622+
623+
unref() {
624+
if (this[kRefed] === true) {
625+
this[kRefed] = false;
626+
if (--immediateInfo[kRefCount] === 0)
627+
toggleImmediateRef(false);
628+
}
629+
return this;
630+
}
631+
632+
hasRef() {
633+
return !!this[kRefed];
634+
}
635+
}
636+
596637
module.exports = {
597638
TIMEOUT_MAX,
598639
kTimeout: Symbol('timeout'), // For hiding Timeouts on other internals.
599640
async_id_symbol,
600641
trigger_async_id_symbol,
601642
Timeout,
643+
Immediate,
602644
kRefed,
603645
kHasPrimitive,
604646
initAsyncResource,

lib/internal/timers/promises.js

+124
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
'use strict';
2+
3+
const {
4+
Promise,
5+
PromiseReject,
6+
} = primordials;
7+
8+
const {
9+
Timeout,
10+
Immediate,
11+
insert
12+
} = require('internal/timers');
13+
14+
const {
15+
hideStackFrames,
16+
codes: { ERR_INVALID_ARG_TYPE }
17+
} = require('internal/errors');
18+
19+
let DOMException;
20+
21+
const lazyDOMException = hideStackFrames((message) => {
22+
if (DOMException === undefined)
23+
DOMException = internalBinding('messaging').DOMException;
24+
return new DOMException(message);
25+
});
26+
27+
function setTimeout(after, value, options = {}) {
28+
const args = value !== undefined ? [value] : value;
29+
if (options == null || typeof options !== 'object') {
30+
return PromiseReject(
31+
new ERR_INVALID_ARG_TYPE(
32+
'options',
33+
'Object',
34+
options));
35+
}
36+
const { signal, ref = true } = options;
37+
if (signal !== undefined &&
38+
(signal === null ||
39+
typeof signal !== 'object' ||
40+
!('aborted' in signal))) {
41+
return PromiseReject(
42+
new ERR_INVALID_ARG_TYPE(
43+
'options.signal',
44+
'AbortSignal',
45+
signal));
46+
}
47+
if (typeof ref !== 'boolean') {
48+
return PromiseReject(
49+
new ERR_INVALID_ARG_TYPE(
50+
'options.ref',
51+
'boolean',
52+
ref));
53+
}
54+
// TODO(@jasnell): If a decision is made that this cannot be backported
55+
// to 12.x, then this can be converted to use optional chaining to
56+
// simplify the check.
57+
if (signal && signal.aborted)
58+
return PromiseReject(lazyDOMException('AbortError'));
59+
return new Promise((resolve, reject) => {
60+
const timeout = new Timeout(resolve, after, args, false, true);
61+
if (!ref) timeout.unref();
62+
insert(timeout, timeout._idleTimeout);
63+
if (signal) {
64+
signal.addEventListener('abort', () => {
65+
if (!timeout._destroyed) {
66+
// eslint-disable-next-line no-undef
67+
clearTimeout(timeout);
68+
reject(lazyDOMException('AbortError'));
69+
}
70+
}, { once: true });
71+
}
72+
});
73+
}
74+
75+
function setImmediate(value, options = {}) {
76+
if (options == null || typeof options !== 'object') {
77+
return PromiseReject(
78+
new ERR_INVALID_ARG_TYPE(
79+
'options',
80+
'Object',
81+
options));
82+
}
83+
const { signal, ref = true } = options;
84+
if (signal !== undefined &&
85+
(signal === null ||
86+
typeof signal !== 'object' ||
87+
!('aborted' in signal))) {
88+
return PromiseReject(
89+
new ERR_INVALID_ARG_TYPE(
90+
'options.signal',
91+
'AbortSignal',
92+
signal));
93+
}
94+
if (typeof ref !== 'boolean') {
95+
return PromiseReject(
96+
new ERR_INVALID_ARG_TYPE(
97+
'options.ref',
98+
'boolean',
99+
ref));
100+
}
101+
// TODO(@jasnell): If a decision is made that this cannot be backported
102+
// to 12.x, then this can be converted to use optional chaining to
103+
// simplify the check.
104+
if (signal && signal.aborted)
105+
return PromiseReject(lazyDOMException('AbortError'));
106+
return new Promise((resolve, reject) => {
107+
const immediate = new Immediate(resolve, [value]);
108+
if (!ref) immediate.unref();
109+
if (signal) {
110+
signal.addEventListener('abort', () => {
111+
if (!immediate._destroyed) {
112+
// eslint-disable-next-line no-undef
113+
clearImmediate(immediate);
114+
reject(lazyDOMException('AbortError'));
115+
}
116+
}, { once: true });
117+
}
118+
});
119+
}
120+
121+
module.exports = {
122+
setTimeout,
123+
setImmediate,
124+
};

lib/timers.js

+20-127
Original file line numberDiff line numberDiff line change
@@ -24,16 +24,10 @@
2424
const {
2525
ObjectCreate,
2626
MathTrunc,
27-
Promise,
28-
SymbolToPrimitive
27+
Object,
28+
SymbolToPrimitive,
2929
} = primordials;
3030

31-
const {
32-
codes: { ERR_INVALID_ARG_TYPE }
33-
} = require('internal/errors');
34-
35-
let DOMException;
36-
3731
const {
3832
immediateInfo,
3933
toggleImmediateRef
@@ -42,14 +36,14 @@ const L = require('internal/linkedlist');
4236
const {
4337
async_id_symbol,
4438
Timeout,
39+
Immediate,
4540
decRefCount,
4641
immediateInfoFields: {
4742
kCount,
4843
kRefCount
4944
},
5045
kRefed,
5146
kHasPrimitive,
52-
initAsyncResource,
5347
getTimerDuration,
5448
timerListMap,
5549
timerListQueue,
@@ -67,6 +61,8 @@ let debug = require('internal/util/debuglog').debuglog('timer', (fn) => {
6761
});
6862
const { validateCallback } = require('internal/validators');
6963

64+
let timersPromises;
65+
7066
const {
7167
destroyHooksExist,
7268
// The needed emit*() functions.
@@ -135,12 +131,6 @@ function enroll(item, msecs) {
135131
* DOM-style timers
136132
*/
137133

138-
function lazyDOMException(message) {
139-
if (DOMException === undefined)
140-
DOMException = internalBinding('messaging').DOMException;
141-
return new DOMException(message);
142-
}
143-
144134
function setTimeout(callback, after, arg1, arg2, arg3) {
145135
validateCallback(callback);
146136

@@ -171,44 +161,14 @@ function setTimeout(callback, after, arg1, arg2, arg3) {
171161
return timeout;
172162
}
173163

174-
setTimeout[customPromisify] = function(after, value, options = {}) {
175-
const args = value !== undefined ? [value] : value;
176-
if (options == null || typeof options !== 'object') {
177-
return Promise.reject(
178-
new ERR_INVALID_ARG_TYPE(
179-
'options',
180-
'Object',
181-
options));
164+
Object.defineProperty(setTimeout, customPromisify, {
165+
enumerable: true,
166+
get() {
167+
if (!timersPromises)
168+
timersPromises = require('internal/timers/promises');
169+
return timersPromises.setTimeout;
182170
}
183-
const { signal } = options;
184-
if (signal !== undefined &&
185-
(signal === null ||
186-
typeof signal !== 'object' ||
187-
!('aborted' in signal))) {
188-
return Promise.reject(
189-
new ERR_INVALID_ARG_TYPE(
190-
'options.signal',
191-
'AbortSignal',
192-
signal));
193-
}
194-
// TODO(@jasnell): If a decision is made that this cannot be backported
195-
// to 12.x, then this can be converted to use optional chaining to
196-
// simplify the check.
197-
if (signal && signal.aborted)
198-
return Promise.reject(lazyDOMException('AbortError'));
199-
return new Promise((resolve, reject) => {
200-
const timeout = new Timeout(resolve, after, args, false, true);
201-
insert(timeout, timeout._idleTimeout);
202-
if (signal) {
203-
signal.addEventListener('abort', () => {
204-
if (!timeout._destroyed) {
205-
clearTimeout(timeout);
206-
reject(lazyDOMException('AbortError'));
207-
}
208-
}, { once: true });
209-
}
210-
});
211-
};
171+
});
212172

213173
function clearTimeout(timer) {
214174
if (timer && timer._onTimeout) {
@@ -276,46 +236,6 @@ Timeout.prototype[SymbolToPrimitive] = function() {
276236
return id;
277237
};
278238

279-
const Immediate = class Immediate {
280-
constructor(callback, args) {
281-
this._idleNext = null;
282-
this._idlePrev = null;
283-
this._onImmediate = callback;
284-
this._argv = args;
285-
this._destroyed = false;
286-
this[kRefed] = false;
287-
288-
initAsyncResource(this, 'Immediate');
289-
290-
this.ref();
291-
immediateInfo[kCount]++;
292-
293-
immediateQueue.append(this);
294-
}
295-
296-
ref() {
297-
if (this[kRefed] === false) {
298-
this[kRefed] = true;
299-
if (immediateInfo[kRefCount]++ === 0)
300-
toggleImmediateRef(true);
301-
}
302-
return this;
303-
}
304-
305-
unref() {
306-
if (this[kRefed] === true) {
307-
this[kRefed] = false;
308-
if (--immediateInfo[kRefCount] === 0)
309-
toggleImmediateRef(false);
310-
}
311-
return this;
312-
}
313-
314-
hasRef() {
315-
return !!this[kRefed];
316-
}
317-
};
318-
319239
function setImmediate(callback, arg1, arg2, arg3) {
320240
validateCallback(callback);
321241

@@ -342,42 +262,15 @@ function setImmediate(callback, arg1, arg2, arg3) {
342262
return new Immediate(callback, args);
343263
}
344264

345-
setImmediate[customPromisify] = function(value, options = {}) {
346-
if (options == null || typeof options !== 'object') {
347-
return Promise.reject(
348-
new ERR_INVALID_ARG_TYPE(
349-
'options',
350-
'Object',
351-
options));
265+
Object.defineProperty(setImmediate, customPromisify, {
266+
enumerable: true,
267+
get() {
268+
if (!timersPromises)
269+
timersPromises = require('internal/timers/promises');
270+
return timersPromises.setImmediate;
352271
}
353-
const { signal } = options;
354-
if (signal !== undefined &&
355-
(signal === null ||
356-
typeof signal !== 'object' ||
357-
!('aborted' in signal))) {
358-
return Promise.reject(
359-
new ERR_INVALID_ARG_TYPE(
360-
'options.signal',
361-
'AbortSignal',
362-
signal));
363-
}
364-
// TODO(@jasnell): If a decision is made that this cannot be backported
365-
// to 12.x, then this can be converted to use optional chaining to
366-
// simplify the check.
367-
if (signal && signal.aborted)
368-
return Promise.reject(lazyDOMException('AbortError'));
369-
return new Promise((resolve, reject) => {
370-
const immediate = new Immediate(resolve, [value]);
371-
if (signal) {
372-
signal.addEventListener('abort', () => {
373-
if (!immediate._destroyed) {
374-
clearImmediate(immediate);
375-
reject(lazyDOMException('AbortError'));
376-
}
377-
}, { once: true });
378-
}
379-
});
380-
};
272+
});
273+
381274

382275
function clearImmediate(immediate) {
383276
if (!immediate || immediate._destroyed)

0 commit comments

Comments
 (0)