-
-
Notifications
You must be signed in to change notification settings - Fork 31.9k
timers: introduce setInterval async iterator #35841
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,11 @@ | ||
'use strict'; | ||
|
||
const { | ||
Symbol, | ||
FunctionPrototypeBind, | ||
Promise, | ||
PromisePrototypeFinally, | ||
PromiseResolve, | ||
PromiseReject, | ||
} = primordials; | ||
|
||
|
@@ -15,7 +17,7 @@ const { | |
|
||
const { | ||
AbortError, | ||
codes: { ERR_INVALID_ARG_TYPE } | ||
codes: { ERR_INVALID_ARG_TYPE, ERR_INVALID_ARG_VALUE } | ||
} = require('internal/errors'); | ||
|
||
function cancelListenerHandler(clear, reject) { | ||
|
@@ -125,7 +127,170 @@ function setImmediate(value, options = {}) { | |
() => signal.removeEventListener('abort', oncancel)) : ret; | ||
} | ||
|
||
function setInterval(after, value, options = {}) { | ||
const args = value !== undefined ? [value] : value; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: for validation prefer using |
||
if (options == null || typeof options !== 'object') { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. function f(o = {}) {
console.log({ o })
}
f(null) Outputs
This is a copy from There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah so it can be null but not undefined - you can check |
||
throw new ERR_INVALID_ARG_TYPE( | ||
'options', | ||
'Object', | ||
options); | ||
} | ||
const { | ||
signal, | ||
ref = true, | ||
// Defers start of timers until the first iteration | ||
wait = false, | ||
// This has each invocation of iterator.next set up a new timeout | ||
timeout: asTimeout = false, | ||
// Skips intervals that are missed | ||
skip = false | ||
// Clears entire queue of callbacks when skip = true and the callbacks well missed the timeout | ||
} = options; | ||
if (signal !== undefined && | ||
(signal === null || | ||
typeof signal !== 'object' || | ||
!('aborted' in signal))) { | ||
throw new ERR_INVALID_ARG_TYPE( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ditto regarding |
||
'options.signal', | ||
'AbortSignal', | ||
signal); | ||
} | ||
if (typeof ref !== 'boolean') { | ||
throw new ERR_INVALID_ARG_TYPE( | ||
'options.ref', | ||
'boolean', | ||
ref); | ||
} | ||
return { | ||
[Symbol.asyncIterator]() { | ||
// asTimeout can't skip as they always have intervals between each iteration | ||
const resolveEarlyEnabled = !asTimeout && skip; | ||
let timeout, | ||
callbacks = [], | ||
active = true, | ||
missed = 0; | ||
|
||
setIntervalCycle(); | ||
|
||
const iterator = { | ||
async next() { | ||
if (!active) { | ||
return { | ||
done: true, | ||
value: undefined | ||
}; | ||
} | ||
// TODO(@jasnell): If a decision is made that this cannot be backported | ||
// to 12.x, then this can be converted to use optional chaining to | ||
// simplify the check. | ||
if (signal && signal.aborted) { | ||
return PromiseReject(new AbortError()); | ||
} | ||
return new Promise( | ||
(resolve, reject) => { | ||
callbacks.push({ resolve, reject }); | ||
if (missed > 0) { | ||
resolveNext(); | ||
} | ||
setIntervalCycle(); | ||
} | ||
); | ||
}, | ||
async return() { | ||
active = false; | ||
clear(); | ||
resolveAll({ | ||
done: true, | ||
value: undefined | ||
}); | ||
if (signal) { | ||
signal.removeEventListener('abort', oncancel); | ||
} | ||
return { | ||
done: true, | ||
value: undefined | ||
}; | ||
} | ||
}; | ||
if (signal) { | ||
signal.addEventListener('abort', oncancel, { once: true }); | ||
} | ||
return iterator; | ||
|
||
function setIntervalCycle() { | ||
if (!active) { | ||
return; | ||
} | ||
if (timeout) { | ||
return; | ||
} | ||
// Wait and asTimeout both imply a callback is required before setting up a timeout | ||
if (!callbacks.length && (wait || asTimeout)) { | ||
return; | ||
} | ||
missed = 0; | ||
const currentTimeout = timeout = new Timeout(() => { | ||
if (asTimeout && currentTimeout === timeout) { | ||
// No need to clear here as we set to not repeat for asTimeout | ||
timeout = undefined; | ||
} | ||
resolveNext(); | ||
}, after, undefined, !asTimeout, true); | ||
if (!ref) timeout.unref(); | ||
insert(timeout, timeout._idleTimeout); | ||
} | ||
|
||
function resolveNext() { | ||
if (!callbacks.length) { | ||
if (resolveEarlyEnabled) { | ||
missed += 1; | ||
} | ||
return; | ||
} | ||
const deferred = callbacks.shift(); | ||
if (deferred) { | ||
const { resolve } = deferred; | ||
resolve({ | ||
done: false, | ||
value | ||
}); | ||
missed -= 1; | ||
} | ||
if (missed > 0 && callbacks.length) { | ||
// Loop till we have completed each missed interval that we have a callback for | ||
resolveNext(); | ||
} | ||
} | ||
|
||
function resolveAll(value) { | ||
callbacks.forEach(({ resolve }) => resolve(value)); | ||
callbacks = []; | ||
} | ||
|
||
function rejectAll(error) { | ||
callbacks.forEach(({ reject }) => reject(error)); | ||
callbacks = []; | ||
} | ||
|
||
function clear() { | ||
if (timeout) { | ||
// eslint-disable-next-line no-undef | ||
clearTimeout(timeout); | ||
timeout = undefined; | ||
} | ||
} | ||
|
||
function oncancel() { | ||
clear(); | ||
rejectAll(new AbortError()); | ||
} | ||
|
||
} | ||
}; | ||
} | ||
|
||
module.exports = { | ||
setTimeout, | ||
setImmediate, | ||
setInterval, | ||
}; |
Uh oh!
There was an error while loading. Please reload this page.