-
Notifications
You must be signed in to change notification settings - Fork 6
/
Copy path07s-latency-compensation.md.erb
187 lines (134 loc) · 12.8 KB
/
07s-latency-compensation.md.erb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
---
title: Компенсація затримки передачі даних
slug: latency-compensation
date: 0007/01/02
number: 7.5
points: 5
sidebar: true
photoUrl: http://www.flickr.com/photos/ikewinski/9473352049/
photoAuthor: Mike Lewinski
contents: Дізнаєтеся що таке компенсація затримки передачі даних. | Сповільните виконання програми та простежите що відбувається. | Дізнаєтеся як Методи Meteor'a викликають один одного.
paragraphs: 28
---
У попередньому розділі ми представили новий концепт зі світу Meteor: **Методи**.
<%= diagram "latency1", "Без компенсації затримки передачі даних", "pull-right" %>
Метод Meteor -- це спосіб організовано виконати серію команд на сервері. У нашому прикладі ми використовували Метод щоб додати до нових постів ім'я автора, id автора, а також поточний час і дату на сервері.
Однак, якщо Meteor виконував би Методи найпримітивнішим способом, у нас з'явилися б проблеми. Уявіть собі наступний ланцюжок подій (часові інтервали тут просто випадкові цифри цілях ілюстрації):
- *+0ms:* Користувач тисне кнопку Submit і браузер викликає Метод.
- *+200ms:* Сервер вносить зміни базу даних Mongo.
- *+500ms:* Клієнт отримує змінені дані і оновлює інтерфейс щоб їх відобразити
Якби Meteor працював саме так, у нас була б затримка між дією користувача і відображенням цієї дії додатком (затримка буде більш-менш відчутна _ залежності як далеко ви перебуваєте від сервера). У сучасному веб додатку подібне запізнення неприпустимі!
### Компенсація затримки
<%= diagram "latency2", "З компенсацією затримки передачі даних", "pull-right" %>
Щоб уникнути подібних проблем Meteor використовує концепцію під назвою **Компенсація Затримки** (**Latency Compensation**). Коли ми створили Метод `post`, ми додали його у файл у папці `collections/`. Це означає, що він доступний як серверу, так і *клієнту* - і обидва можуть запустити Метод одночасно!
Коли ви викликаєте Метод, клієнт посилає запит на сервер. Але він також одночасно *симулює* виклик Методу на колекції клієнта. Наш ланцюжок подій буде мати наступний вигляд:
- *+0ms:* Користувач натискає на кнопку Submit. Браузер викликає Метод на сервері.
- *+0ms:* Клієнт симулює дію Методу на своїх локальних колекціях, і тут же відображає його результат в інтерфейсі.
- *+200ms:* Сервер вносить зміни в базу даних Mongo.
- *+500ms:* Клієнт отримує відповідь від сервера з результатом операції. Клієнт скасовує симулювання змін і замінює їх справжніми, які прийшли з сервера (які, як правило, збігаються з симульованими). Інтерфейс користувача оновлюється, щоб відобразити зміни, якщо вони є.
В результаті користувач бачить зміни миттєво. Коли додаток отримає відповідь від сервера , інтерфейс додатку може помінятися (а може і залишитися незмінним) щоб відобразити справжні зміни. Щоб ці зміни залишалися мінімальними, нам потрібно якомога краще симулювати реальні документи.
### Спостерігаємо за компенсацією затримки
Додамо невелику зміну у Методі `post` щоб поспостерігати за діями клієнта і сервера. Щоб зробити це, ми використаємо `Meteor._sleepForMs()` функцію для затримки виклику методу на 5 секунд, але (важливо) *лише на сервері*.
Ми використаємо `isServer` для запиту Meteor чи виконується метод на клієнті (метод “stub”) або на сервері. Метод [stub](http://docs.meteor.com/#methods_header) -- це симуляція методу, яку запускає Meteor паралельно на клієнті, в той же час "справжній" метод виконується на сервері.
Ми дізнаємося у Meteor чи виконується код на сервері. Якщо так - ми затримаємо речі на 5 секунд і добавимо `(server)` в кінець заголовка нового поста. Якщо ні, додамо рядок `(client)`:
~~~js
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)";
// чекаємо 5 секунд
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
};
}
});
~~~
<%= caption "collections/posts.js" %>
<%= highlight "11~17" %>
Якщо б ми тут зупинились, то демонстрація була б не досить повною. Починаючи з цього моменту, це виглядадиме неначе форма відправки посту поставлена на паузу на 5 секунд перед редиректом вас на загальний список постів і більш нічого не відбувається.
Щоб зрозуміти чому, давайте вернемося до обробника події відправлення посту:
~~~js
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});
});
}
});
~~~
<%= caption "client/templates/posts/post_submit.js" %>
Ми розмістили наш виклик маршруту `Router.go()` всередині методу виклику колбеку. Що значить, що форма чекає на успіх методу перед редиректом.
Тепер це насправді правильний курс дій. Після всього ви не можете редиректити користувача перед тим, як ви взнаєте відправлення посту пройшло чи ні, тому що буде помилково спочатку відредиректитись, а далі через декілька секунд повернутись до початкової форми відправки посту для скорегування помилок.
У цьому прикладі, ми хочемо бачити результат наших дій миттєво. Тому ми змінимо виклик маршруту на редірект на маршрут `postsList` (ми не можетмо направити на пост, тому що ми не знаємо його `_id` поза методом), винесіть його поза колбеком і подивіться що відбудеться:
~~~js
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');
}
});
~~~
<%= caption "client/templates/posts/post_submit.js" %>
<%= highlight "20" %>
<%= scommit "7-5-1", "Демонструємо порядок появи постів за допомогою методу sleep." %>
Якщо ми створимо пост, ми наочно побачимо компенсацію затримки в дії. Спочатку пост з'явиться з рядком `(client)` в заголовку (перший пост у списку, з посиланням на GitHub):
<%= screenshot "s5-1", "Наш пост збережений _ колекції клієнта" %>
Потім, 5 секунд по тому, він замінюється цим документом з сервера:
<%= screenshot "s5-2", "Наш пост після відповіді сервера" %>
### Методи колекцій клієнта
Після всього цього можна подумати, що Методи досить складні. Насправді вони можуть бути дуже прості. Ми вже побачили три дуже простих Методи для редагування колекцій - `insert`, `update` і `remove`.
Коли ви створюєте нову колекцію `posts`, ви також створюєте три Методи: `posts/insert`, `posts/update` і `posts/delete`. Іншими словами, коли ви викликаєте `Posts.insert()` у колекції клієнта, ви викликаєте Метод з компенсацією затримки, який робить дві речі:
1. Перевіряє чи є у нього можливість редагувати колекцію викликаючи функції `allow` і `deny`.
2. Редагує локальну колекцію.
### Методи викликають Методи
Якщо ви ще не втратили нитку розповіді, ймовірно ви тільки що помітили, що наш Метод `post` викликає інший Метод (`posts/insert`) коли ми створюємо наш пост. Як це працює?
Коли запускається симуляція (версія Методу на клієнті), ми також запускаємо симуляцію методу `insert` (таким чином додаючи новий об'єкт в колекцію на клієнті). Але ми *Не викликаємо* справжній, серверний `insert` - ми очікуємо що *серверна* версія методу `post` зробить це.
Послідовно, коли серверна версія методу `post` викликає `insert`, нам не потрібно турбуватися щодо симуляції, і об'єкт успішно створюється в головній базі даних.
Як і раніше, не забувайте усунути ваші зміни перед тим як починати новий розділ.