Transcript
  • Meteor

Flushing Computations

From the class:  Tracker

When you invalidate a computation, it doesn't re-run your function right away. It actually queues up the re-run to happen at a later time in a process called flushing. I want to talk in this lesson about what it means to flush. It sounds a little bit gross, but it actually means something pretty straightforward.

And I want to talk about some of the vocabulary that gets used around flushing. So in this paragraph, for example, it talks about delaying until the system is idle, but what does it actually mean for the system to be idle, and what does it mean for things to be predictable? So let's go over and take a look at what it means to flush.

When you see the word flush, it normally means to take a queue. So think of a queue as an array, that has a bunch of computations in it-- say C1, C2, C3 and so on. And to flush means to go through that entire array and to empty it, to empty the array. And what it's going to do is to empty this array and re-run each of these computations in the queue, or in the array. And then by the time this is done, this array will be empty or it'll be flushed.

So when you call invalidate on a computation, the computation gets added to this special queue here, this array. And then on the next tick of the event loop, the next tickk-- So in other words, when you call a set time out, for example, to run some code at a later time, that's an example of the next tick of event loop. If you were to call said time out with zero, that's when this array is going to be processed.

So that's what it means for the system to be idle means that their current call stack is empty in that we're executing the next waiting thing on the event loop. So if you want to learn more about the event loop, you can go watch the JavaScript runtime class where we talk about it at length.

Now, the other property of this flushing process is that the computation won't be added to this array more than once. And so if a computation is already invalid, then it's already on the array and it doesn't get added multiple times. So this means that we'll only re=run an invalid computation once per cycle, or per tick, and it helps to keep the system performance. So let me go show you a couple of examples of this in code.

I've cleaned up the code quite a bit to make this simpler to read. And what I'm going to do is add just one extra function here called Invalidate. I'll make it a global function so we can call it from the browser, the debugger.

And inside this function, I'm going to call the Invalidate function of the computation. So Invalidate of the computation. And then just print out to the log that we've called Invalidate. And then I'm going to put a debugger statement to drop us in the debugger so we can take a look at the stack. All right. Did we spell everything right? Good. And now press Save and let that refresh in the debugger.

Now I'm going to call that Invalidate function. And let's look through what's actually happened here. Now the first thing to note is that we call this Invalidate function, but the computation doesn't rerun right away. And if it had, you would notice that "hello world" would have been printed to the console below here. It would have been printed right here. But it's not, right?

What instead is printed is the next line, called Invalidate. Don't get confused by this up here. This was just printed the first time the computation was run.

But right after we call Invalidate, nothing happens here. We just go right on to the next line. And eventually we end up here in the debugger.

So what's happened is that Invalidate has queued up this computation, queued it up into that array-- I'll just call it C1-- to be flushed later. And so it would be the same thing-- very similar to if you called Set Timeout and then passed in a function with a 0 time that would say on the next tick of the event loop, go ahead and run this function.

Now the other thing to point out is the call stack itself. Notice that it's not empty. So we've called a bunch of these sort of strange functions here, and then eventually we ended up in our Invalidate function that we called here.

Now as I start, let me clear this up. To return from these functions, the call stack starts to empty. And right before it completely empties, what we're saying here is that the call stack is active. So in other words, the system is not idle. The system is not idle.

But once you return, and there's no more functions to execute-- I'll just press Play to speed this up a little bit-- the next thing, the next function waiting on the queue will be executed. And now notice that-- forget this error message down here about WebSockets, we just took too long-- notice that "hello world" gets printed to the console next. So the order of operations here was that first we called our Invalidate function here, and then second, we print out this line called Invalidated. And then third, at a later time, the function actually re-runs when the system is idle, or the call stack is empty and the next tick of the event loop happens.

Now one of the other benefits of this is that we can control how many times the computation actually gets re-run. So let's say that the computation gets invalidated multiple times within a function. Let me show you an example.

Inside of the Invalidate function, let's copy this line a few times. So come in here and we'll copy it. And we'll just hit call Invalidate, Invalidate, Invalidate, Invalidate. You can imagine a scenario like this would happen if, inside of one of your helpers, you had multiple reactive data sources and they were all invalidating a computation at the same time.

Well one of the benefits of queueing up the computation to re-run later is that the system, or Meteor, can be smart enough to say you know, this computation has already been invalidated and added to the queue. We don't need to add it again. So the queue of things waiting to be flushed will only have one computation added. And then when we try to do it again and again and again, Meteor will say it's already been added, so we don't need to add it again. So if we press Play here, what we should expect is that called Invalidated happens once. And then the function only re-runs one time.

So to prove that, let's call Invalidate. And we'll press Play to work through the debugger. And here we go. Called Invalidate gets printed and, "hello world" only gets printed once.

So the key benefit of this is that it's more performant. Instead of running this function again and again, the function might actually be kind of expensive, and there's really only a need to run it once. And that's one of the benefits of this approach.

Occasionally, it makes sense to tell Meteor to flush all the computations right at once, instead of waiting until the next tick of the event loop. You can do that by calling the Flush method of Tracker. And this can be useful if you're writing tests, or even sometimes in your application you might want to force all the consequences of running this function to happen right away instead of waiting until another tick.

So I'll get rid of this debug statement. We don't really need it anymore. And keep an eye on what happens when we call the Invalidate function, and how the order of things has changed. So now this time, we call Invalidate up here, and then "hello world" gets printed to the console. So our function gets re-run right away.

And then down below, after the Flush call, it says called Invalidate. So this now, instead of being the second step, is the third step. So our function gets re-run right away. And this doesn't wait for the call stack to be empty. It just does it right away.

Sometimes it can be useful to know when the Flush cycle has completed. And so Meteor provides a function that we can call where we can provide a callback that will get called after the Flush is complete. Now one of the tricks to this-- let me show you first how to do it. So if you say tracker.afterflush and call the After Flush function. And you can pass it a callback. And in our callback, we can do whatever we would like. But I'm just going to say we're after flush.

Now this will get-- this After Flush callback will be removed after it's called. So generally, if you're going to use the After Flush callback, you have to put it inside of the function that's to be re-run. And that's so that these callbacks don't stack up over and over again. So they just get removed after the Flush is complete. So this way, we'll add it back to the queue on each cycle.

To see how that works over in the debugger, we'll refresh here. And notice it says After Flush right away. And that's because as soon as this function gets called, or as soon as we call Auto Run, the function runs once, and then the flush completes once.

Then we'll force another flush by calling Invalidate. And you can see that our callback gets called. And so Invalidate happens, and then the flush happens, where our function gets re-run. And then the After Flush callback gets called, and then finally this next line runs. And so the important thing is, as the final thing of this flush, the final thing that happens, our After Flush callbacks-- this is a callback-- the callback will get called.

So After Flush can be pretty useful even in your applications, because it's a place to put some code that's guaranteed to run after all of the computations have finished. So for example, if some data ends up changing the DOM in the application, you can use an After Flush to say I want to run some code after that has happened.