Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Different behaviour of setTimeout in nodejs and Chrome

The code sample is -

global.a = 'aaa';

const obj = {
    a: 'a',
    desc() {
        console.log(this);
        console.log(this.a);
    }
}

setTimeout(obj.desc, 2000)

When I run this code in nodejs, I get the following output:

Timeout {
  _called: true,
  _idleTimeout: 2000,
  _idlePrev: null,
  _idleNext: null,
  _idleStart: 79,
  _onTimeout: [Function: desc],
  _timerArgs: undefined,
  _repeat: null,
  _destroyed: false,
  [Symbol(asyncId)]: 6,
  [Symbol(triggerAsyncId)]: 1 }
undefined

But the same code, with global changed to window in Chrome/Firefox prints aaa and the window object, which is what this MDN doc says and which is what I expect.

I was under the impression that nodejs and Chrome both use Google's v8 JS engine to execute JavaScript. So why is the output different? Is there something more to this? I tried searching but couldn't find satisfactory answers.

Node version - v9.10.
Chrome's version - Version 70.0.3538.110

like image 498
arfat Avatar asked Nov 28 '18 22:11

arfat


People also ask

How does setTimeout work in Nodejs?

The setTimeout() executes the function passed in the first argument after the time specified in the second argument. The setTimeout function doesn't block other code and the rest of the code is executed and after a specified time the code inside setTimeout function is executed.

What is the difference between setTimeout and window setTimeout?

Assuming we're talking about browser-based JavaScript: No difference. setTimeout() simply omits the window. , which is implied. The effect they have is exactly the same.

Does Nodejs have setTimeout?

The setTimeout function is used to call a function after the specified number of milliseconds. The delay of the called function begins after the remaining statements in the script have finished executing. The setTimeout function is found in the Timers module of Node. js.

What is the difference between setTimeout and setImmediate and process nextTick?

setImmediate() vs process.setTimeout() is processed in the Check handlers phase, while process. nextTick() is processed at the starting of the event loop and between each phase of the event loop. On any given context process. nextTick() has higher priority over setImmediate() .


2 Answers

node.js has its own implentation of timers which is different from browser implementations (although they can generally be used in the same way). This isn't really documented, but when you use setTimeout, it creates an instance of a Timer class with the callback passed as an argument. This callback is assigned to a property of the Timer instance which is used later:

  • Callback assigned as property of the Timer instance
  • Callback called as instance function later

This means that the this context for timers in node.js incidentally gets set to the Timer instance itself. Browsers apparently do something different.

This is mostly a semantic issue in your original code: when you pass function properties from objects they lose their context. You could use .bind to retain the context, or use another function to call desc directly on obj to retain the context.

setTimeout(obj.desc.bind(obj), 2000);
setTimeout(() => obj.desc(), 2000);

These two will log obj and obj.a in both node.js and browser environments.

like image 59
Explosion Pills Avatar answered Oct 21 '22 23:10

Explosion Pills


To answer your question, we must consult the source code for timers, since NodeJS' setTimeout and vanilla JS' setTimeout are not the same thing.

If we look here, we will find the definition for setTimeout. We must pay attention to what happens with the callback that is passed in:

What happens is it is passed into the Timeout constructor:

const timeout = new Timeout(callback, after, args, false);

Ok, so what is the Timeout class? We can find that here. Notice the line:

this._onTimeout = callback;

Ok, we stick the callback in an instance member, so I suspect that's how it is being called.

If we go back to the other source file and search for _onTimeout, we will find this line:

timer._onTimeout();

So in this case, because of how the callback is being called, timer is in fact what this refers to (as you've noted by logging this from the callback), and since the timer (or Timeout instance) does not have any such property (a), it logs undefined.

In vanilla JS, it behaves differently, and this refers to the window in your case.

As you may know, you can resolve this inconsistency by simply binding the callback to obj in setTimeout like so:

setTimeout(obj.desc.bind(obj), 2000);
like image 1
pushkin Avatar answered Oct 21 '22 22:10

pushkin