Why Is Settimeout(Fn, 0) Sometimes Useful

Why is setTimeout(fn, 0) sometimes useful?

In the question, there existed a race condition between:

  1. The browser's attempt to initialize the drop-down list, ready to have its selected index updated, and
  2. Your code to set the selected index

Your code was consistently winning this race and attempting to set drop-down selection before the browser was ready, meaning that the bug would appear.

This race existed because JavaScript has a single thread of execution that is shared with page rendering. In effect, running JavaScript blocks the updating of the DOM.

Your workaround was:

setTimeout(callback, 0)

Invoking setTimeout with a callback, and zero as the second argument will schedule the callback to be run asynchronously, after the shortest possible delay - which will be around 10ms when the tab has focus and the JavaScript thread of execution is not busy.

The OP's solution, therefore was to delay by about 10ms, the setting of the selected index. This gave the browser an opportunity to initialize the DOM, fixing the bug.

Every version of Internet Explorer exhibited quirky behaviors and this kind of workaround was necessary at times. Alternatively it might have been a genuine bug in the OP's codebase.


See Philip Roberts talk "What the heck is the event loop?" for more thorough explanation.

What is setTimeout doing when set to 0 milliseconds?

A few useful facts might help clarify what's happening:

  1. JavaScript is single-threaded. Asynchronous callbacks are assigned to a message placed in a message queue.
  2. When no code is currently executing, the event loop polls the message queue, requesting the next message in line to be processed (executed).
  3. setTimeout adds a message (with the callback provided) to the end of this queue after the specified delay has elapsed.

(Note: this means the delay in a setTimeout call is not a sure thing; it is the minimum delay before the callback is executed. The actual time taken depends on how long it takes to process any messages ahead of it in the queue.)

So what happens if the delay is set to 0? A new message is added to the queue immediately, and will be processed when the currently executing code is finished and any previously-added messages have been processed.

What's happening in your code

When you invoke setTimeout

setTimeout(function() { 
console.log('AAA');
}, 0);

…a message gets added to the queue with the specified callback. The rest of your code…

for (i = 0; i < 1000; i++) {
console.log('BBB');
}
// etc.

…continues executing synchronously. Once it has completely finished, the event loop polls the message queue for the next message and finds the one with your setTimeout callback, which is then processed (the callback is run).

The callback only ever gets executed after the currently executing code has finished, no matter how long that takes.

Further reading

For more details on the event loop, see:

  • Concurrency model and event loop—MDN
  • The JavaScript event loop: explained—Carbon Five

Why is setTimeOut,0 used in ngAfterContentInit()?

First of all, I want to say this is bad practice and you should avoid it. However I am not gonna lie, I have used this trick a few times myself as well.

The previous developer added setTimeout because A, there is probably a race condition between the behavior of various (Angular) components or B, one component changes the state of another component in a change detection cycle (indicated by the error).

The behavior the developer achieved

First, I need to explain what the developer achieves by setting his function into a setTimeout with zero delay. Javascript is a single-threaded language, meaning by default the JS engine does one thing at a time.

The super simplified (and kind of wrong) way of explaining this is that a JS engine has a list of statements to execute in a list and it always picks up the first. When you set something in a setTimeout you put that statement (your function call) into the end of this task list, so it will be executed when everything else "queued" up for execution until that point has been processed.

There is a great video on YouTube about this: What the heck is the event loop anyway?, I strongly encourage you to go and watch this video!

In case of a race condition

It might happen that two of your components has a race condition on each other, to demonstrate let's say the JS engine "list of tasks" looks like this:

  • component A does some stuff
  • component A wants to set some stuff in its child: component B
  • Angular runs a CD cycle and attempt to render your component
  • child component B does some stuff

The problem here is that in step 2 your child component (B) has not been created yet, so attempting to use it will throw an error. By putting the statement which modifies component B in a setTimeout your list of tasks will look like this:

  • component A does some stuff
  • Angular runs a CD cycle and attempt to render your component
  • child component B does some stuff
  • component A wants to set some stuff in its child: component B

This way your B component will exist by the time it has been created.

In case of an inconsistent state

Angular runs a so-called change detection cycle to check what has changed in the application state and how the UI needs to be updated. In developer mode, this check runs twice for the same cycle and the outputs are compared. If there is a difference the framework will throw the mentioned ExpressionChangedAfterItHasBeenCheckedError error to warn you about this.

At this point, you can say to yourself, great this issue won't appear in prod so I am good but you are not good this issue will lead to a worsened performance as Angular will run more change detection cycles than it should because it thinks something has changed when in reality you didn't intend to change anything. So you should track down the route cause of this problem and fix it.

The official Angular website has a dedicated document page for this with a video guide on how to solve the problem. There is also a detailed Stackoverflow answer here about this behavior and why is this checked.

So as for your original question by adding the setTimeout the developer tricks Angular to pass this double-checking because the updateContentChildren function will be executed only after the current change detection has finished. This means your internal state is always "one tick ahead" of the UI as some work is always finished after the CD cycle has finished.

What is the difference between setTimeout(fn, 0) and setTimeout(fn, 1)?

I think the answer is "It depends" now.

We can run the code in different platform and browsers:

function setTimeouts() {
setTimeout(function() { console.log(2); }, 2);
setTimeout(function() { console.log(1); }, 1);
setTimeout(function() { console.log(0); }, 0);
}

for (var i = 0; i < 10; i++) {
setTimeouts();
}

NodeJS - setTimeout(fn,0) vs setImmediate(fn)

setTimeout is simply like calling the function after delay has finished. Whenever a function is called it is not executed immediately, but queued so that it is executed after all the executing and currently queued eventhandlers finish first. setTimeout(,0) essentially means execute after all current functions in the present queue get executed. No guarantees can be made about how long it could take.

setImmediate is similar in this regard except that it doesn't use queue of functions. It checks queue of I/O eventhandlers. If all I/O events in the current snapshot are processed, it executes the callback. It queues them immediately after the last I/O handler somewhat like process.nextTick. So it is faster.

Also (setTimeout,0) will be slow because it will check the timer at least once before executing. At times it can be twice as slow. Here is a benchmark.

var Suite = require('benchmark').Suite
var fs = require('fs')

var suite = new Suite

suite.add('deffered.resolve()', function(deferred) {
deferred.resolve()
}, {defer: true})

suite.add('setImmediate()', function(deferred) {
setImmediate(function() {
deferred.resolve()
})
}, {defer: true})

suite.add('setTimeout(,0)', function(deferred) {
setTimeout(function() {
deferred.resolve()
},0)
}, {defer: true})

suite
.on('cycle', function(event) {
console.log(String(event.target));
})
.on('complete', function() {
console.log('Fastest is ' + this.filter('fastest').pluck('name'));
})
.run({async: true})

Output

deffered.resolve() x 993 ops/sec ±0.67% (22 runs sampled)
setImmediate() x 914 ops/sec ±2.48% (57 runs sampled)
setTimeout(,0) x 445 ops/sec ±2.79% (82 runs sampled)

First one gives idea of fastest possible calls. You can check yourself if setTimeout gets called half as many times as other. Also remember setImmediate will adjust to your filesystem calls. So under load it will perform less. I don't think setTimeout can do better.

setTimeout is un-intrusive way of calling functions after some time. Its just like its in the browser. It may not be suited for server-side (think why I used benchmark.js not setTimeout).



Related Topics



Leave a reply



Submit