Skip to content
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

Chapter 1: event loop with different result #3

Open
ghost opened this issue Oct 21, 2021 · 2 comments
Open

Chapter 1: event loop with different result #3

ghost opened this issue Oct 21, 2021 · 2 comments

Comments

@ghost
Copy link

ghost commented Oct 21, 2021

page number: 13
node: v14.17.3
macOs Big Sur: Version 11.6

const sleep_st = (t) => new Promise((r) => setTimeout(r, t)); 
const sleep_im = () => new Promise((r) => setImmediate(r));
    (async () => {
      setImmediate(() => console.log(1));
      console.log(2);
      await sleep_st(0);
      setImmediate(() => console.log(3));
      console.log(4);
      await sleep_im();
      setImmediate(() => console.log(5));
      console.log(6);
      await 1;
      setImmediate(() => console.log(7));
      console.log(8);
})();

log out:
2 4 1 3 6 8 5 7

setImmediate(() => console.log(1))
console.log(2)
Promise.resolve().then(() =>
	setTimeout(() => {
		setImmediate(() => console.log(3))
		console.log(4)
		Promise.resolve().then(() =>
			setImmediate(() => {
				setImmediate(() => console.log(5))
				console.log(6)
				Promise.resolve().then(() => {
					setImmediate(() => console.log(7))
					console.log(8)
				})
			})
		)
	}, 0)
)

log out:
2 1 4 3 6 8 5 7

@tlhunter
Copy link
Owner

Thanks for submitting this. I've reproduced the error. I'll try to find the issue and submit it to the publisher.

@Badredin
Copy link

Badredin commented Nov 18, 2022

Hello,

I've started reading this book and stumbled upon the same behavior.

After studying how callbacks, promises, async/await, and the Node.js event loop works, it turns out that this output, i.e., 2 1 4... or 2 4 1... is normal. Actually, both snippets are equivalent and exhibit the same behavior which can be observed if executed multiple enough times.

So, why is this happening? In Event Loop Explained it states the following:

When Node.js starts, it initializes the event loop, processes the provided input script ... which may make async API calls, schedule timers, or call process.nextTick(), then begins processing the event loop.

So, when we execute these code snippets in a standalone script, the event loop is only initialized and not running yet. The loop will start only after the execution of the full script which happens outside of an I/O cycle. Now, if a script is outside of an I/O cycle the setImmediate() and setTimeout() timers' callback execution order is non-deterministic (setImmediate() vs setTimeout()).

To have a deterministic order of execution we have to make these calls in an I/O cycle. For example, the two code snippets could be written like this:

const fs = require('fs');

const sleep_st = (t) => new Promise((r) => setTimeout(r, t));
const sleep_im = () => new Promise((r) => setImmediate(r));

fs.readFile(__filename, () => {
    (async () => {
        setImmediate(() => console.log(1));
        console.log(2);
        await sleep_st(0);
        setImmediate(() => console.log(3));
        console.log(4);
        await sleep_im();
        setImmediate(() => console.log(5));
        console.log(6);
        await 1;
        setImmediate(() => console.log(7));
        console.log(8);
    })();
});
const fs = require('fs');

fs.readFile(__filename, () => {
    setImmediate(() => console.log(1));
    console.log(2);
    Promise.resolve().then(() => setTimeout(() => {
        setImmediate(() => console.log(3));
        console.log(4);
        Promise.resolve().then(() => setImmediate(() => {
            setImmediate(() => console.log(5));
            console.log(6);
            Promise.resolve().then(() => {
                setImmediate(() => console.log(7));
                console.log(8);
            });
        }));
    }, 0));
});

In both cases the output is ensured to be the following: 2 1 4 3 6 8 5 7

The confusion is most probably caused by the book's statement that the poll phase is the first phase of the event loop and that the script execution is happening during this phase, which is actually a good model of thinking when there are I/O operations involved, although to be strict the first phase is the timers phase and can be demonstrated in the following script:

const fs = require('fs');

setTimeout(() => {
    console.log('timeout');
}, 100);

setImmediate(() => {
    console.log('setImmediate');
});

const startTiming = Date.now();

fs.readFile(__filename, () => {
    console.log('readFile callback');
});

while (Date.now() - startTiming < 1000) {}

Note that setTimeout() and setImmediate() are called outside of an I/O cycle and the output is:

timeout
setImmediate
readFile callback

And based on all the above analysis, if these calls are made inside an I/O cycle the setImmediate() callback will be called before the setTimeout() callback:

const fs = require('fs');

fs.readFile(__filename, () => {
    setTimeout(() => {
        console.log('timeout');
    }, 0);

    // This delay is not necessary, it's just used for empasis.
    const startTiming = Date.now();
    while (Date.now() - startTiming < 1000) {}

    setImmediate(() => {
        console.log('setImmediate');
    });

    console.log('readFile callback');
});

and as expected the output is:

readFile callback
setImmediate
timeout

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants