forked from DiscoverMeteor/DiscoverMeteor_Ru
-
Notifications
You must be signed in to change notification settings - Fork 0
/
07s-latency-compensation.md.erb
133 lines (93 loc) · 11 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
---
title: Компенсация задержки передачи данных
slug: latency-compensation
date: 0007/01/02
number: 7.5
sidebar: true
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` чтобы понаблюдать за действиями клиента и сервера. Для этого мы напишем слегка продвинутый код с помощью npm пакета `futures` - он позволит нам замедлить создание объектов в Методе.
Мы воспользуемся свойством `isSimulation` чтобы узнать у Meteor если метод был вызван как `stub`. [Stub](http://docs.meteor.com/#methods_header) это та самая симуляция Метода на клиенте, которую Meteor запускает параллельно с настоящим Методом на сервере.
Мы узнаем у Meteor выполняется ли код на клиенте. Если да - добавим строку `(client)` в конец заголовка нового поста. Если нет, добавим строку `(server)`:
~~~js
Meteor.methods({
post: function(postAttributes) {
// […]
// выбираем нужные поля для публикации
var post = _.extend(_.pick(postAttributes, 'url', 'message'), {
title: postAttributes.title + (this.isSimulation ? '(client)' : '(server)'),
userId: user._id,
author: user.username,
submitted: new Date().getTime()
});
// ждем 5 секунд
if (! this.isSimulation) {
var Future = Npm.require('fibers/future');
var future = new Future();
Meteor.setTimeout(function() {
future.return();
}, 5 * 1000);
future.wait();
}
var postId = Posts.insert(post);
return postId;
}
});
~~~
<%= caption "collections/posts.js" %>
<%= highlight "6, 7, 13~22" %>
Внимание: если вы задумались что означает `this` в `this.isSimulation` - это так называемый [Method invocation object](http://docs.meteor.com/#meteor_methods) (объект вызова Метода) который дает доступ к разным полезным переменным.
Подробный разбор пакета [Futures](https://npmjs.org/package/future) выходит за рамки нашей книги. Но если вкратце - мы просто сообщили Meteor выждать 5 секунд перед тем как добавлять новый объект в коллекцию на сервере.
Мы также совершим перенаправление пользователя на страницу со списком постов:
~~~js
Template.postSubmit.events({
'submit form': function(event) {
event.preventDefault();
var post = {
url: $(event.target).find('[name=url]').val(),
title: $(event.target).find('[name=title]').val(),
message: $(event.target).find('[name=message]').val()
}
Meteor.call('post', post, function(error, id) {
if (error)
return alert(error.reason);
});
Router.go('postsList');
}
});
~~~
<%= caption "client/views/posts/post_submit.js" %>
<%= highlight "15" %>
<%= 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`, нам не нужно беспокоиться насчет симуляции, и объект успешно создается в главной базе данных.