在上一章,我們介紹了 Meteor 的一個(gè)新概念:內(nèi)置方法。
Meteor 的內(nèi)置方法是一種在服務(wù)器上執(zhí)行一系列命令的結(jié)構(gòu)化方法。在示例中,我們使用內(nèi)置方法是為了確保新帖子是通過作者的姓名和 ID ,以及當(dāng)前服務(wù)器時(shí)間去標(biāo)記。
然而,如果 Meteor 用最基本的方式去執(zhí)行內(nèi)置方法,我們會(huì)注意到一些問題。想一想下面事件的序列(注:為演示方便,時(shí)間戳值是隨機(jī)的生成的):
如果這是 Meteor 的操作方式,它會(huì)有一個(gè)很短的時(shí)間差去看到這樣的執(zhí)行操作的結(jié)果(延時(shí)的多少會(huì)取決于你的服務(wù)器性能)。但我們不可能讓這些情況出現(xiàn)在 Web 應(yīng)用程序中!
為了避免這個(gè)問題,Meteor 引入了一個(gè)叫做延遲補(bǔ)償(Latency Compensation)的概念。如果我們把 post
方法的定義放在 collections/
目錄下。這意味著它在服務(wù)端和客戶端上都存在,而且是同時(shí)運(yùn)行!
當(dāng)你使用內(nèi)置方法的時(shí)候,客戶端會(huì)發(fā)送請(qǐng)求到服務(wù)器去調(diào)用,同時(shí)還模仿服務(wù)器內(nèi)置方法去操作本地?cái)?shù)據(jù)庫集合。所以現(xiàn)在我們的工作流程是:
這樣用戶就會(huì)立刻看到變化。服務(wù)器的響應(yīng)返回一段時(shí)間后,根據(jù)服務(wù)器數(shù)據(jù)庫發(fā)送過來的更改請(qǐng)求,本地?cái)?shù)據(jù)庫可能會(huì)或可能不會(huì)有明顯的改變。因此,我們應(yīng)該學(xué)會(huì)確保本地?cái)?shù)據(jù)盡可能地與服務(wù)器數(shù)據(jù)庫保持一致。
我們可以對(duì) post
內(nèi)置方法的調(diào)用稍作改動(dòng)。為此,我們將會(huì)通過 npm 包 futures
,使用一些高級(jí)的編程方式去把延遲對(duì)象放到我們的內(nèi)置方法調(diào)用里面。
我們將使用 isServer
去問 Meteor 現(xiàn)在所調(diào)用的內(nèi)置方法是在客戶端被調(diào)用(作為一個(gè)存根 Stub)或是在服務(wù)器端。這個(gè)存根 stub 是模仿內(nèi)置方法在客戶端運(yùn)行的模擬方法,而“真正的”內(nèi)置方法是在服務(wù)器上運(yùn)行的。
所以我們會(huì)詢問 Meteor 這部分代碼是否在服務(wù)器端執(zhí)行。如果是,我們會(huì)在帖子的標(biāo)題后面添加 (server)
字符串。如果不是,我們將添加 (client)
字符串:
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
};
}
});
如果我們到此為止,這個(gè)演示就不那么有意義。當(dāng)前,看起來就像是帖子表單提交后暫停了5秒鐘,然后轉(zhuǎn)到主帖子列表,沒發(fā)生其他事情。
為了理解這是為什么,讓我們看看帖子提交的事件 handler:
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});
});
}
});
我們?cè)诜椒?call 回調(diào)函數(shù)中放了 Router.go()
路由函數(shù)。
現(xiàn)在的行為通常是正確的。畢竟,你不能在確定他們帖子提交是否有效之前去跳轉(zhuǎn)用戶,只是因?yàn)槿绻⒓刺D(zhuǎn)走,然后幾秒鐘后再轉(zhuǎn)回到原始帖子頁面去更正數(shù)據(jù),這會(huì)非常令人困惑。
但是對(duì)于這個(gè)例子而言,我們想立即看看結(jié)果。所以我們將路由更改到 postsList
路由(我們還不能路由到帖子,因?yàn)樵诜椒ㄖ馕覀儾恢捞拥?_id
),把它從回調(diào)函數(shù)中移出來,看看會(huì)發(fā)生什么:
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');
}
});
如果我們現(xiàn)在創(chuàng)建一個(gè)帖子,我們可以清楚地看到延遲補(bǔ)償。首先,插入一個(gè)標(biāo)題帶 (client)
的帖子(列表的第一個(gè)帖子,鏈接到 GitHub):
接著,五秒之后,它就會(huì)被服務(wù)器插入的真正帖子文檔所替代:
通過上面所說的,你可能會(huì)認(rèn)為內(nèi)置方法很復(fù)雜,但事實(shí)上它們也可以相當(dāng)簡(jiǎn)單。實(shí)際上我們已經(jīng)用過三個(gè)非常簡(jiǎn)單的內(nèi)置方法:集合的操作方法 insert
、update
和 remove
。
當(dāng)你定義一個(gè)服務(wù)器集合稱為 'posts'
,你已經(jīng)隱式地定義了這三個(gè)內(nèi)置方法: posts/insert
、posts/update
和 posts/delete
。換句話說,當(dāng)你在本地集合中調(diào)用 Posts.insert()
,你已經(jīng)在調(diào)用延時(shí)補(bǔ)償方法來做下面這兩件事:
allow
和 deny
方法的回調(diào)去操作集合(然而這并不需要發(fā)生在內(nèi)置方法的模擬)。你可能已經(jīng)意識(shí)到當(dāng)我們插入帖子的時(shí)候,post
的內(nèi)置方法調(diào)用了另一個(gè)內(nèi)置方法(posts/insert
)。這是如何工作的呢?
當(dāng)模擬方法(客戶端版本的內(nèi)置方法)開始運(yùn)行,模擬方法執(zhí)行 insert
(插入的是本地集合)時(shí),我們不叫它真正的服務(wù)器端 insert
,但我們會(huì)認(rèn)為服務(wù)器端的 post
也將會(huì)同樣的被插入。
因此,當(dāng)服務(wù)器端的 post
調(diào)用內(nèi)置方法 insert
的時(shí)候更加沒有必要去擔(dān)心的客戶端模擬方法,它肯定能夠在客戶端順利地插入。
像之前一樣,在閱讀下一章之前,不要忘記還原你所做的更改。
更多建議: