May 10, 2021 Meteor
In the last chapter, we introduced a new concept for Meteor: the built-in approach.
Meteor's built-in approach is a structured way to execute a series of commands on the server. In the example, we use the built-in method to ensure that new posts are marked by the author's name and ID, as well as the current server time.
However, if Meteor executes the built-in method in the most basic way, we'll notice some problems. Think about the sequence of events below (Note: Timestamp values are randomly generated for demonstration purposes):
If this is how Meteor works, it will have a short time difference to see the results of such an operation (the amount of delay will depend on the performance of your server). But we can't make these situations appear in web applications!
To avoid this problem, Meteor
introduced a concept called Latency Compensation.
I
f we put
post
of the
collections/
This means that it exists on both the service side and the client side and is running at the same time!
When you use the built-in method, the client sends a request to the server to call, while also mimicking the server's built-in method to manipulate the local database collection. So now our workflow is:
This allows the user to see the change immediately. A fter the server's response returns for some time, the local database may or may not change significantly depending on the change request sent from the server database. Therefore, we should learn to ensure that local data is as consistent as possible with the server database.
We can make a
post
to the call to the post built-in method.
To do this, we'll use some advanced programming to put deferred objects into our built-in method calls through npm package
futures
isServer
to ask if the built-in method that Meteor is calling now is to be called on the client side (as a stub Stub) or on the server side.
This stub is
a simulation that mimics
the built-in method that runs on the client side, while the "real" built-in method runs on the server.
So we'll ask Meteor if this part of the code is executed on the server side. I
f so, we'll add a string after
(server)
post.
If not, we'll
(client)
string:
Posts = new Mongo.Collection('posts');
Meteor.methods({
postInsert: function(postAttributes) {
check(this.userId, String);
check(postAttributes, {
title: String,
url: String
});
if (Meteor.isServer) {
postAttributes.title += "(server)";
// wait for 5 seconds
Meteor._sleepForMs(5000);
} else {
postAttributes.title += "(client)";
}
var postWithSameLink = Posts.findOne({url: postAttributes.url});
if (postWithSameLink) {
return {
postExists: true,
_id: postWithSameLink._id
}
}
var user = Meteor.user();
var post = _.extend(postAttributes, {
userId: user._id,
author: user.username,
submitted: new Date()
});
var postId = Posts.insert(post);
return {
_id: postId
};
}
});
If we get there, this demo doesn't make much sense. Currently, it looks like you paused for 5 seconds after the post form was submitted, then moved on to the main post list and nothing else happened.
To understand why, let's take a look at the event handler submitted by the post:
Template.postSubmit.events({
'submit form': function(e) {
e.preventDefault();
var post = {
url: $(e.target).find('[name=url]').val(),
title: $(e.target).find('[name=title]').val()
};
Meteor.call('postInsert', post, function(error, result) {
// display the error to the user and abort
if (error)
return alert(error.reason);
// show this result but route anyway
if (result.postExists)
alert('This link has already been posted');
Router.go('postPage', {_id: result._id});
});
}
});
We put the
Router.go()
routing function in the method callback function.
The behavior is usually correct now. After all, you can't jump users before you're sure if their post submission is valid, just because it can be confusing if you jump away immediately and then go back to the original post page a few seconds later to correct the data.
But for this example, we want to see the results immediately.
So we changed the route to
postsList
(we can't route to the post yet, because we
_id
of the post outside of the method), moved it out of the callback function, and let's see what happens:
Template.postSubmit.events({
'submit form': function(e) {
e.preventDefault();
var post = {
url: $(e.target).find('[name=url]').val(),
title: $(e.target).find('[name=title]').val()
};
Meteor.call('postInsert', post, function(error, result) {
// display the error to the user and abort
if (error)
return alert(error.reason);
// show this result but route anyway
if (result.postExists)
alert('This link has already been posted');
});
Router.go('postsList');
}
});
If we create a post now, we can clearly see the delay compensation.
First, insert a post with
(client)
first post in the list, linked to GitHub):
Then, after five seconds, it is replaced by the real post document inserted by the server:
From what you said above, you might think built-in methods are complicated, but in fact they can be quite simple.
In fact, we've used three very simple built-in methods: the collection's
insert
update
remove
When you define a server collection called
'posts'
implicitly defined these three
posts/insert
posts/update
posts/delete
In other words, when you call
Posts.insert()
you're already calling the delay compensation method to do both:
allow
by calling
deny
to the allow and deny methods (however this does not need to occur in the simulation of the built-in method).
You may have realized that when we insert a
post
built-in method calls another built-in
posts/insert
How does this work?
When the impersonation method (the built-in method of the client version) starts running and
the simulation method
insert
(inserted into a local collection), we don't call it the real
post
be inserted the same way.
insert
Therefore, when the server-side
post
method
insert
there is even more unnecessary to worry about the client impersonation method, which is sure to be inserted smoothly on the client side.
As before, don't forget to restore your changes before reading the next chapter.