Transcript
  • Meteor
  • Tutorial

Methods

Meteor methods allow us to asynchronously call functions on the server. In this episode, we're going to get into how it works with a few examples. Let's say I have a simple application where I can submit messages. When I do that, I have a label beneath the form that updates reactively.

As you can see, the label changes to reflect the message submitted in the form. Over in my project, you can see I've got a client folder and a server folder. This way, Meteor will serve the client files to the browser, and the server files will stay on the server.

In client.html, I have two templates-- add message and show message. The show message template just shows the reactive message property. Over in my JavaScript, I'm going to respond to the submit form event. And within the event handler, I'm going to grab the form and then parse it using a custom method I created. I'll explain it in a second. But it returns an object of fields and values.

And then I'll set a session variable with the value of the message. Below in the show_message template helpers, I have a message function that returns the value of message stored in the reactive session object. And so the user interface will update automatically when the message changes.

The parseForm method helps me extract the fields of the form into an object. The first thing I do is I call serializeArray, which is a jQuery method that just goes through the entire form and returns an array of objects that have names and values. And then I'll iterate over the array and create an object that has the field names as keys and the field values as the values. And I'll just return that object.

But let's say instead of just setting a session variable, I'd like the server to parse the message for users and turn the users into links. So let's create a Meteor method to do that. On the right hand side, I've opened up server.js from the server directory. Let's create a function closure and then create our first Meteor method.

I'll call up Meteor.methods and pass in a map of different methods. So I'll create one called createMessage. And it'll take one parameter called fields. Inside the method, the first thing I'll do is pull out the message from the fields object. And then let's create a regular expression that looks for the at symbol and then a word and applies that globally. We'll create a parseForUsers function, which we'll fill out in a second.

And then we'll call message.replace and pass in the regular expression and a call back, which it will call for each match and return the value. Now inside parseForUsers, what we'll do is we're going to replace the at symbol with an empty string. And that will be our user.

And then I'll create a link. The link can be whatever you want. And then I'll return some HTML with the link. And I'll make it bold so that we can find it.

OK, over in the client, let's call our method. We'll do that by using the Meteor.call function. And the first parameter will be the name of the method followed by any number of parameters, and then finally a callback function that takes an error followed by the return value of the method. And for now, I'll assume there's no error, and I'll set the message key of session with the message result returned from the method.

To recap, on the left I have my browser code. And on the right, I have my server code. And when I make a Meteor call, it asynchronously sends a request to the server. And the return value is passed as the second parameter to the callback function. And then I can use that message inside of the callback function to set the session variable.

OK, over in the browser, let's see if it works. I'll use the at symbol with my name. And when I hit Submit, great, I get a link back. But what if the server takes a while to respond? In other words, what if there's server latency?

Let me throw in a bogus loop here to simulate a long running call. The loop will just block the method for a couple seconds. Let's go back to the browser and see what that looks like. I'll type in a message. And when I hit Submit, I have to wait a couple seconds, and then the user interface updates with the result.

To solve this problem, I can run what's called a method stub on the browser. If I define the same method on the client, that method will be run at the same time that the server method is called asynchronously. So inside the method, I can set the session variable right away while I'm waiting to hear back from the server.

Let's try it out in the browser. When I submit the form, you can see the label is updated immediately. And then when I get the result from the server, it's updated again. To make sure it's clear, let's jump into the debugger.

I clicked on Sources, and then I'll open up the client.js file, and I'll set a breakpoint inside the method stub. Then I'll scroll down and set another breakpoint inside of the method callback. This callback will get called when the server returns a result.

OK, let's jump up to the form and submit it again. I'll type "Hi, Chris," and submit. And I jump right into my method stub breakpoint. And over on the right, if I expand this, I can see that it points to Meteor._MethodInvocation. And it has a special property called IsSimulation, which is set to true. This means that this method is running on the client.

I'll press Play again and wait for the server to return its result. That'll put me into my callback method. You can see the result of the method call from the server is passed in as the second parameter to the callback. And then the UI gets a link.

Let's refactor our code to take advantage of that isSimulation property that we just saw. I want to move all of the createMessage method code into its own file. So what I can do is to create a new folder called lib and inside it create a JavaScript file called methods.js. We can call this anything we want.

Then I'll cut and paste the Meteor method. And I'll check whether or not this.isSimulation is true. And if it is, I know I'm running on the client. And I'm just going to set the session variable right away with whatever the value of the message field is. And then I'll return. If I'm running on the server, the rest of the code will execute.

OK, over in the browser, let's set a breakpoint on our new if statement and submit the form again. When I do that, I can see that this.isSimulation is set to true, because we're in the browser.

To conclude, let's look at what we can do to handle errors. Let's say on the server, we had a list of users. I'm only going to have two, let's say Chris and Bob. And if I try and parse a user that doesn't exist by checking whether index of user is equal to negative 1, then I can throw an error. And whenever I throw new Meteor.Error, the first parameter being some kind of code or string, the second being the reason, and the third parameter being some details, that error will be sent back to the client.

And so in our client code, let's go into our callback and actually handle the error. So if there's an error set-- in other words, it's not undefined-- let's just go ahead and set the message to the error code and then the error reason. And just so we can inspect the object, I'm going to print the error object out to the console. And if there's no error, I'll set the message key equal to the message value that's returned.

In the browser, let's submit a form with a user who doesn't exist in the list. When the server returns the result, I can see the Meteor error printed to the console with the error as the first parameter, the reason as the second, and the details as the third. Just as a note, the error code is typically an HTTP status code, in this case the 404 indicating not found.

If you'd like to download the source code for this episode, I've created a GitHub repository at the Evented Mind organization. You can clone the Meteor methods repository. I've also created a separate branch for each part of the video. Each branch is documented in the readme. You can play around on your own computer and check out the specific branch that interests you. Or just to see the final result, you can look at the master branch.