perf: use promises rather than setImmediate so that I/O is executed within the current task stack#1100
Conversation
|
Is there any kind of test or samples you can provide on this? I don't have a big problem with this as is since it gets rid of a file, but I'd like to understand a bit more about the background and (scale of) impact if possible |
|
Inside wrapAsync, it is implemented using setImmediate. In this case, it will create a new macro task at the end of the JavaScript task queue. The actual I/O operation will only commence when the macro task is executed. That is to say, when the user initiates a readFile request, it will be inserted at the end of the JavaScript task queue. The actual I/O operation will only start after the current JavaScript task queue has been completely executed. When using microtasks (Promise), it is equivalent to inserting a high-priority task into the task queue. It will be executed immediately at the end of the current macro task instead of being placed at the end of all macro tasks. When there is a for loop that executes readFile within the loop and there are a large number of setTimeout calls in the program, the execution speed will become very slow. |
|
test.a.js function wrap(callback) {
setImmediate(() => {
callback();
});
}
function readFile(file, callback) {
wrap(() => {
// nothing
callback();
});
}
let timer = null;
function wait() {
timer = setTimeout(() => {
const date = Date.now();
while (Date.now() - date < 100) {
// nothing
}
wait();
});
}
(async () => {
wait();
console.time('A');
for (let i = 0; i < 1000; i++) {
await new Promise(resolve => {
readFile('a.txt', resolve);
});
}
console.timeEnd('A');
clearTimeout(timer);
})();test.b.js function wrap(callback) {
Promise.resolve().then(() => {
callback();
});
}
function readFile(file, callback) {
wrap(() => {
// nothing
callback();
});
}
let timer = null;
function wait() {
timer = setTimeout(() => {
const date = Date.now();
while (Date.now() - date < 100) {
// nothing
}
wait();
});
}
(async () => {
wait();
console.time('B');
for (let i = 0; i < 1000; i++) {
await new Promise(resolve => {
readFile('a.txt', resolve);
});
}
console.timeEnd('B');
clearTimeout(timer);
})();You can try to execute the above two test scripts: node test.a.js && node test.b.jsNote that do not execute two tests in one file, as some mechanisms in js make the one that is executed first slower |
|
@G-Rath |
setImmediate so that I/O is executed within the current task stack
G-Rath
left a comment
There was a problem hiding this comment.
you're all good, I just hadn't gotten back around to this - I have updated the title to reflect this is a performance change and describe better what the actual change was, but that's just a nit thing :)
## [4.17.2](v4.17.1...v4.17.2) (2025-05-15) ### Performance Improvements * use promises rather than `setImmediate` so that I/O is executed within the current task stack ([#1100](#1100)) ([786072f](786072f))
|
🎉 This PR is included in version 4.17.2 🎉 The release is available on: Your semantic-release bot 📦🚀 |

If setImmediate is used, when there are too many setTimeout, IO will be delayed in execution, and this execution will cause the IO operation interval to become extremely long.
Referring to the NodeJS source code, I/O operations should use microtasks