Skip to content

Commit 6857619

Browse files
committed
playing with the event loop
0 parents  commit 6857619

File tree

5 files changed

+280
-0
lines changed

5 files changed

+280
-0
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
node_modules

index.js

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
#!/usr/bin/env node
2+
3+
// https://medium.com/the-node-js-collection/what-you-should-know-to-really-understand-the-node-js-event-loop-and-its-metrics-c4907b19da4c
4+
// https://blog.risingstack.com/node-js-at-scale-understanding-node-js-event-loop/#event-loop
5+
// https://html.spec.whatwg.org/multipage/webappapis.html#task-queue
6+
7+
// The event loop as a process is a set of phases with specific tasks that are processed in a round-robin manner.
8+
9+
const chalk = require('chalk');
10+
const fs = require('fs');
11+
12+
const logger = {
13+
// timers
14+
t: function() {
15+
console.log(chalk.blue(' timers phase queue'), ...arguments);
16+
},
17+
// poll phase (IO Callbacks)
18+
// it has a hard maximum (system dependent) before it stops polling for more events.
19+
p: function() {
20+
console.log(chalk.yellow(' poll phase queue'), ...arguments);
21+
},
22+
// pending callbacks (some system operations)
23+
pc: function() {
24+
console.log(chalk.gray('pending callbacks'), ...arguments);
25+
},
26+
/**
27+
* nextTick is not really the next tick, its just `immediate` and then
28+
* setImmediate is not really immediate at all, its more like the next tick
29+
* - Bert Belder
30+
*/
31+
// check
32+
// runs immediately after the poll phase completed or has become idle
33+
// fires on the following iteration or 'tick' of the event loop
34+
immediate: function() {
35+
console.log(chalk.magenta(' check phase'), ...arguments);
36+
},
37+
// Tick - Will resolve before the event loop continues.
38+
// It allows you to "starve" your I/O by making recursive process.nextTick() calls
39+
// Alternative phrasing:
40+
// Allow the script to run to completion before executing the callback.
41+
tick: function() {
42+
console.log(chalk.cyan(' tick'), ...arguments);
43+
},
44+
// close events
45+
ce: function() {
46+
console.log(chalk.gray('close events'), ...arguments);
47+
},
48+
};
49+
50+
function shuffle(array) {
51+
for (let i = array.length - 1; i > 0; i--) {
52+
const j = Math.floor(Math.random() * (i + 1));
53+
[array[i], array[j]] = [array[j], array[i]];
54+
}
55+
}
56+
57+
function blockQueue(ms) {
58+
const startCallback = Date.now();
59+
while (Date.now() - startCallback < ms) {
60+
// do nothing
61+
}
62+
}
63+
64+
const arr = [
65+
66+
() => {
67+
const m = 0;
68+
const scheduled = Date.now();
69+
setTimeout(function() {
70+
const delay = Date.now() - scheduled;
71+
logger.t(`timeout ${m} (delay ${delay})`);
72+
}, m);
73+
console.log(`setTimeout(${m}) scheduled`);
74+
},
75+
76+
() => {
77+
const m = 1000;
78+
const scheduled = Date.now();
79+
setTimeout(function() {
80+
const delay = Date.now() - scheduled - m;
81+
logger.t(`timeout ${m} (delay ${delay})`);
82+
}, m);
83+
console.log(`setTimeout(${m}) scheduled`);
84+
},
85+
86+
() => {
87+
const m = 0;
88+
const scheduled = Date.now();
89+
const id = setInterval(function() {
90+
const delay = Date.now() - scheduled;
91+
logger.t(`interval ${m} (delay ${delay})`);
92+
clearInterval(id);
93+
}, m);
94+
console.log(`setInterval(${m}) scheduled`);
95+
},
96+
97+
() => {
98+
const total = 2;
99+
let count = 1;
100+
const m = 1000;
101+
const scheduled = Date.now();
102+
const id = setInterval(function() {
103+
let delay = Date.now() - scheduled;
104+
delay -= m * count;
105+
logger.t(`interval ${m} ${count}/${total} (delay ${delay})`);
106+
count++;
107+
if (count > total) clearInterval(id);
108+
}, m);
109+
console.log(`setInterval(${m}) scheduled`);
110+
},
111+
112+
/*() => {
113+
const m = 1000;
114+
fs.readFile('index.js', function(err/*, data/) {
115+
if (err) throw err;
116+
blockQueue(m);
117+
logger.p(`read index.js (blocking ${m})`);
118+
});
119+
console.log(`readFile (block ${m}) scheduled`);
120+
},*/
121+
122+
() => {
123+
const m = 2000;
124+
fs.readFile('index.js', function(err/*, data*/) {
125+
if (err) throw err;
126+
blockQueue(m);
127+
logger.p(`read index.js (blocking ${m})`);
128+
129+
global.setImmediate(function() {
130+
logger.immediate('setImmediate');
131+
});
132+
});
133+
console.log(`readFile (block ${m}) scheduled`);
134+
135+
},
136+
137+
() => {
138+
// setImmediate() is actually a special timer that runs in a separate phase of the event loop.
139+
// It uses a libuv API that schedules callbacks to execute after the poll phase has completed.
140+
/**
141+
* The main advantage to using setImmediate() over setTimeout() is setImmediate() will always be executed
142+
* before any timers if scheduled within an I/O cycle, independently of how many timers are present.
143+
*/
144+
global.setImmediate(function() {
145+
logger.immediate('setImmediate');
146+
});
147+
console.log(`setImmediate scheduled`);
148+
},
149+
150+
() => {
151+
process.nextTick(function() {
152+
logger.tick('nextTick');
153+
});
154+
console.log(`nextTick scheduled`);
155+
},
156+
157+
];
158+
159+
function run() {
160+
shuffle(arr);
161+
162+
console.log(chalk.white('start scheduling'));
163+
for (const fn of arr) {
164+
fn();
165+
}
166+
console.log(chalk.white('done scheduling\n'));
167+
}
168+
169+
170+
global.setImmediate(function() { console.log('on main: immediate'); });
171+
global.setTimeout(function() { console.log('on main: timeout'); }, 0);
172+
process.nextTick(function() { console.log('on main: next tick'); });
173+
174+
run();
175+
176+
console.log('on main: event loop for main complete');

package.json

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"name": "event-loop",
3+
"version": "1.0.0",
4+
"description": "",
5+
"main": "index.js",
6+
"scripts": {
7+
"test": "echo \"Error: no test specified\" && exit 1"
8+
},
9+
"author": "givanse",
10+
"license": "MIT",
11+
"dependencies": {
12+
"chalk": "^2.4.2"
13+
}
14+
}

timeout-vs-immediate.js

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
#!/usr/bin/env node
2+
3+
function test() {
4+
setImmediate(() => {
5+
console.log('immediate');
6+
});
7+
8+
setTimeout(() => {
9+
console.log('timeout');
10+
}, 0);
11+
}
12+
13+
function testAsync() {
14+
const p2 = new Promise(function(resolve) {
15+
setImmediate(() => {
16+
resolve('immediate');
17+
});
18+
});
19+
20+
const p1 = new Promise(function(resolve) {
21+
setTimeout(() => {
22+
resolve('timeout');
23+
}, 0);
24+
});
25+
26+
return Promise.all([p1, p2]);
27+
}
28+
29+
async function run() {
30+
for (let i = 0; i < 5; i++) {
31+
test();
32+
}
33+
34+
for (let i = 0; i < 5; i++) {
35+
const str = await testAsync();
36+
console.log(str.join(''));
37+
}
38+
}
39+
40+
run();
41+

yarn.lock

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
2+
# yarn lockfile v1
3+
4+
5+
ansi-styles@^3.2.1:
6+
version "3.2.1"
7+
resolved "https://artifacts.riotgames.com:443/artifactory/api/npm/npm/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d"
8+
integrity sha1-QfuyAkPlCxK+DwS43tvwdSDOhB0=
9+
dependencies:
10+
color-convert "^1.9.0"
11+
12+
chalk@^2.4.2:
13+
version "2.4.2"
14+
resolved "https://artifacts.riotgames.com:443/artifactory/api/npm/npm/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
15+
integrity sha1-zUJUFnelQzPPVBpJEIwUMrRMlCQ=
16+
dependencies:
17+
ansi-styles "^3.2.1"
18+
escape-string-regexp "^1.0.5"
19+
supports-color "^5.3.0"
20+
21+
color-convert@^1.9.0:
22+
version "1.9.3"
23+
resolved "https://artifacts.riotgames.com:443/artifactory/api/npm/npm/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8"
24+
integrity sha1-u3GFBpDh8TZWfeYp0tVHHe2kweg=
25+
dependencies:
26+
color-name "1.1.3"
27+
28+
color-name@1.1.3:
29+
version "1.1.3"
30+
resolved "https://artifacts.riotgames.com:443/artifactory/api/npm/npm/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
31+
integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=
32+
33+
escape-string-regexp@^1.0.5:
34+
version "1.0.5"
35+
resolved "https://artifacts.riotgames.com:443/artifactory/api/npm/npm/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
36+
integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=
37+
38+
has-flag@^3.0.0:
39+
version "3.0.0"
40+
resolved "https://artifacts.riotgames.com:443/artifactory/api/npm/npm/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
41+
integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0=
42+
43+
supports-color@^5.3.0:
44+
version "5.5.0"
45+
resolved "https://artifacts.riotgames.com:443/artifactory/api/npm/npm/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
46+
integrity sha1-4uaaRKyHcveKHsCzW2id9lMO/I8=
47+
dependencies:
48+
has-flag "^3.0.0"

0 commit comments

Comments
 (0)