NextTick vs setTimeout in Vue

TL;DR : what you want is probably setTimeout.

To understand the following, you may want to watch (or watch again) the famous ‘What the heck is the event-loop?‘ video by Philip Roberts. Five years old, and still relevant. Watch till the end, because we’re interested in the following three aspects of the way browsers render javascript:

  1. call stack
  2. render queue
  3. (async) callback queue / task queue

Philip Roberts only touches on the render queue briefly at the end, but for us it’s crucial.

The call-stack always has priority. When it’s empty, the browser will start outputting what’s in the render queue. When that’s done it’s time for the callback queue.

Basic problem

When you use Vue to calculate something difficult, like opening all the nodes in a tree with thousands of nodes (actual use-case), it takes a while. For the user, this may be confusing. So you want to give them some indication that: ‘yes, we’re working on it’, say some sort of animation. You probably also want to disable the button they just clicked.

The problem is that without some extra work, the browser will show the animation just as it finishes doing that long calculation. By which time you’ve turned the animation off. Pseudo-code sample:

this.animation = true;
this.calculationStart();
this.calculationStop();
this.animation = false

-> browser shows change in tree, animation never called.

What happens is that the render-queue isn’t called until both those calculations are done. Not good. What you want is something like this:

this.animation = true;

-> browser shows animation

this.calculationStart();

-> browser continues to show animation

this.calculationEnds();
this.animation = false;

> browser shows change in tree and stops showing animation. User happy (or happyish, because it did take a while)

So how come the second doesn’t happen? Because the render queue waits till the call stack is empty, before actually rendering. Our calculationStart ends up on the call stack and blocks it. The render queue doesn’t even get called, until the calculation is done.

Not good.

nextTick?

So how do we get our animation to start in the browser, before the calculation starts? You may have picked up on ‘nextTick’ and thought – that sounds promising.

In order to wait until Vue.js has finished updating the DOM after a data change, you can use Vue.nextTick(callback) immediately after the data is changed. The callback will be called after the DOM has been updated.

Vue documentation

Sounds good right? So I might do something like this:

this.animation = true;
this.nextTick( this.calculationStart() );
this.calculationEnds();
this.animation = false;

Unfortunately, this still doesn’t show our animation.

Sigh.

The ‘why’ is hidden in this stack-overflow answer:

nextTick allows you to do something after you have changed the data and VueJS has updated the DOM based on your data change, but before the browser has rendered those changed on the page.

Normally, devs use native JavaScript function setTimeout to achieve similar behavior. But, using setTimeoutrelinquishes control over to the browser before it gives control back to you via your callback.

Prasant at stack overflow

So – VueJS has updated the DOM, but the browser hasn’t actually RENDERED those DOM-changes yet. In other words: we’re still working within the call-stack. [or within microtasks, but for our purposes: same difference].

That’s not what we want. We want the DOM to change before that long calculation starts so our user knows what’s going on.

The solution is also given by Prasant: we use setTimeout. In most cases it’s quite enough to use 0 seconds, though I’ve sometimes gone for more.

So here’s our final pseudocode:

this.animation = true ;

-> browser renders animation because nothing left on the call-stack.

this.setTimeout( function(){this.calculationStart() }, 0)

-> browser moves this.calculationStart from callback queue onto the call stack and does ’this.calculationStart’

this.calculationEnds()
this.animation = false

Animation stops after calculation is done. Phew.

So what happened? setTimeout does it’s thing by putting it’s callback on the callback queue. The callback queue is only called by the browser after both the call stack and the render queue are done. So our animation (which is on the call stack) gets done, the render queue renders it, and then the browser is ready for the callback queue and calls the long-calculation.

This is what we want.

Geef een reactie

Deze site gebruikt Akismet om spam te verminderen. Bekijk hoe je reactie-gegevens worden verwerkt.