Transcript
  • Meteor
  • Tutorial

What is Meteor.bindEnvironment?

In the last video, we talked about dynamically scoped variables using Meteor dot environment variable. In this video, I'll reiterate how that works. And then we'll look at what Meteor dot bind environment does.

Let me walk you through the code. This is all server side, and we're going to be making calls to a Meteor method called Print Name by using the console. When I call Print Name I pass up a name from the client. And then inside of a timeout function, about five seconds later, I call the log method, passing some message to the log method.

And if I jump up into the log method, I'm demonstrating here that I don't want to have to adjust the log function's signature of parameters by adding a name parameter. I just want to be able to grab that from a global variable. So I just pass along the message, hello, and then the log function grabs the name from the global name variable. And then prints out the message, plus a comma, and then the name.

So I set that global name variable down here. So when I call this function, I assign underscore name equal to the name that's passed in. Of course, the problem with this code is that I wait five seconds before calling the log method. And the global variable name could change in the meantime. Let me show you in the console.

OK, I've got my Meteor server running on the right hand side, and on the left, I'll use the DDP command line tools. And first, I'll say DDP, call the print name function and I'll pass Chris up as a parameter. And then, before five seconds passes, I'll make another call, but this time with the name Joe. And if you keep your eye over on the right hand side, you can see now that both times I print out Hello Joe. And of course, that's because the second time I make the Meteor call, I reassign the global variable name to Joe, even though it started off as Chris.

So to fix this, let's use Meteor environment variables. I'm going to create a variable called Current Name and set that equal to a new Meteor dot environment variable. And then down in the Meteor Method, I'm going to call current name dot with value, and pass the name as the first parameter. And then a function as the second. And then I'll move this code inside of the callback function.

And inside my log function, what I'll do is I'll get the current name by saying current name dot get. OK, let's make the DDP call again and see what happens. So a couple seconds will go by. And then over in the right, I get an exception. And the exception error message says that Meteor code must always run within a / so what exactly does that mean. And why is that happening in this case?

Let's go back to the code. Inside of my print name function, I use the environment variable, current name, and say current name dot with value, which sets the name as a global dynamically scoped variable. And remember that it attaches it to the current running fiber. So when this Meteor Method gets called, Meteor automatically creates a new fiber for us to run it.

But then what I do is I call set timeout, which puts a function back onto the event loop at a later time. It does this asynchronously. So about five seconds later, this function gets put back onto the event loop, and we call the log function. But by the time this function gets passed, put back onto the event loop, the fiber is done. So the fiber is no longer running-- this is just a regular function that we've put back onto the event loop at a later time.

Then when we call the log function up here, we get the name by calling current name dot get. And when you call a get on this environment variable, it expects that you're running inside a fiber, because that's where it stores these environment variables. And so that error message-- that the current code must always be run in a fiber-- comes from the environment variable, get method.

OK, so to fix this inside of our set timeout function, we might try running this code inside of a new fiber. So I'll call fiber, and then run it immediately and put this inside of the fiber. And at the top here, I need to get a reference to the fibers library. So I'll use MPM dot require fibers. And then down here, I'll create a new fiber, and run the fiber.

And so I would think that that would get rid of the error message. But let's go back and see what happens on the server now.

OK, I'll call print name from DDP, with the parameter, Chris. And we'll wait a couple seconds and see what name gets printed to the console. And I see that even though I passed up the parameter, Chris, the value that gets printed to the console is undefined.

Let's go back to the code and see why that's the case. When I first create the current name environment variable here, if I call current name dot get right now, the value is undefined, because I haven't set it yet with a width value. And then I make the DDP call, calling the Meteor Method print name, and passing up Chris as the parameter.

Then I call current name dot with value, and pass Chris. And what this does is it stores the value of the current name, environment variable, on the currently executing fiber. And then inside of the callback function, I call set timeout.

And in five seconds, I create a new fiber, and call the method here. But so far, there's no way to copy over the value of current name to the new fiber. It exists on the old fiber, just not on the new one. So we need a way to take all of the dynamically scoped variables and copy them over to the new fiber.

The way we can do this is with a method called Meteor got bind environment. To illustrate, I'm going to create a variable called bound function. And assign it equal to the result of calling Meteor dot bind environment. And bind environment takes a function as the first parameter. And then as the second parameter, a function in case there's an error. So if there's an error, I'll just throw it.

And inside of the callback function for bind environment, that's where I'll call the log method. Then down here, I'll say set timeout and I'll pass the bound function as the callback function, along with my five-second delay.

There's a couple of things to note. First is that when you call Meteor dot bind environment, it returns a function. And I've assigned that function to the bound function variable, and then passed it as the first parameter to set timeout. The function that's returned wraps the function that you pass as the first parameter. And what it does is it makes sure that when that function is run, it's run inside of a new fiber-- if you're not already running in a fiber-- or runs inside of the existing fiber.

The second thing it does is it makes sure that whatever environment variables were visible at the time that you called bind environment, are also visible inside of the new fiber. So essentially, it copies over the currently visible environment variables to the new fiber. And then we can use them.

So at this point, even though this function runs later-- because I've put it as the first parameter to this set timeout-- when I call get, the value will be Chris, the value that was passed up to the print name Meteor Method. Because I created a new function by calling bind environment, and then passed that as the first parameter to set timeout. And part of that environment is the current value of current name, which I've set here.

So let's get rid of the old code, and then go try this out.

OK, over in the console, let's call the method once with the first name, and then call it again with a new name. Great, so now everything works properly, we can see that the first name that gets set is Chris, and then we print that out to the console five seconds later. And the second name is Joe, so we see that just because we assign a new global variable here, it doesn't stomp out the name that was assigned the first time. And then we print out Hello Joe on the second line here.

And that's because each fiber that runs gets its own set of environment variables. So when I make the call to the Meteor Method print name up here, a new fiber is created for that method call, and I attach the current name, Chris, to that fiber. And then down here, when I make a new method call, with a parameter, Joe, a new fiber is created, and the current name with the value of Joe is attached to this new fiber.

And when I call Meteor dot bind environment, that returns a new function. And it makes sure that the new function has all of the environment variables that were visible at the time that I called it. And it also makes sure that the function is run inside of either an existing fiber, or a new fiber, if you're not currently running inside one.

So the general rule of thumb is that any time you're going to pass a callback function to an asynchronous function, like set timeout, you want to make sure that you bind the current environment so that you have access to it in your callback function. You might ask, where is this used currently? In other words, what environment variables exist in Meteor Core?

Currently, there's two key environment variables. One is the write fence, which is used inside of Mongo. And so if you've ever tried to make them an insert, or an update, or make a Mongo call inside of a callback, that's why you might get an error saying that your code has to be run inside of a fiber. And the other object that gets attached as an environment variable is a method indication. And that stores things like the current user ID for the current method indication.

I'll cover these two topics in more detail later. So hopefully, this episode gave you more intuition as to what's happening when you call Meteor dot bind environment, as well as why you might be getting some of those errors saying that Meteor code always has to be run inside of a fiber.