nodejs / node

Node.js JavaScript runtime ✨🐢🚀✨
https://nodejs.org
Other
104.76k stars 28.3k forks source link

`nextTick` doesn't execute as expected with `.mjs` #52978

Closed loveholly closed 1 week ago

loveholly commented 1 month ago

Version

v20.13.1

Platform

Darwin 23.3.0 Darwin Kernel Version 23.3.0

Subsystem

No response

What steps will reproduce the bug?

Use the code sample mentioned in the official documentation:

const baz = () => console.log('baz');
const foo = () => console.log('foo');
const zoo = () => console.log('zoo');

const start = () => {
  console.log('start');
  setImmediate(baz);
  new Promise((resolve, reject) => {
    resolve('bar');
  }).then(resolve => {
    console.log(resolve);
    process.nextTick(zoo);
  });
  process.nextTick(foo);
};

start();

// start foo bar zoo baz

Save the code sample above to a file named test.mjs, and then execute it with node test.mjs. Here is the output for test.mjs: start bar foo zoo baz. When I change the file to test.js and re-run it, I will get the message with: start foo bar zoo baz

How often does it reproduce? Is there a required condition?

Always.

What is the expected behavior? Why is that the expected behavior?

It should print: start foo bar zoo baz

What do you see instead?

It will print: start bar foo zoo baz

Additional information

No response

climba03003 commented 1 month ago

Using .mjs or ESM means you are already running inside a async function (that's why top-level await is possible). So, the order should not be same as CommonJS.

benjamingr commented 1 month ago

I'm not sure that makes sense - everything between start and bar is synchronous. The promise constructor runs synchronously. start foo implies both nextTick runs synchronously and the promise constructor asynchronously - definitely an interesting issue!

benjamingr commented 1 month ago

Oh I was confused, I thought you called bar() inside the promise constructor rather than just resolving that promise.

benjamingr commented 1 month ago

Yeah - this makes sense an easier repro is:

Promise.resolve().then(() => {
  console.log('promise callback');
});
process.nextTick(() => console.log('nextTick callback'));

This is the classic "order inside callback" gotcha (i.e. it's first tick weirdness, if you put it inside of a setImmediate it will log the same in both mjs and js).

This needs a docs fix

benjamingr commented 1 month ago

The docs are still wrong and need an update though :)

Naveen-2021ucp1387 commented 1 month ago

is this still open ?

loveholly commented 4 weeks ago

I would like to add another point: if you use .mjs in a bun.sh, you will get the same output as .cjs with the sample code above.