Компенсация задержки передачи данных

Отступление 7.5

На данный момент переведено

В этой главе вы:

  • Узнаете что такое компенсация задержки передачи данных.
  • Замедлите выполнение приложения и проследите что происходит.
  • Узнаете как Методы Meteor'a вызывают друг друга.
  • В прошлой главе мы представили новый концепт из мира Meteor: Методы.

    Без компенсации задержки передачи данных
    Без компенсации задержки передачи данных

    Метод Meteor это способ организованно выполнить серию команд на сервере. В нашем примере мы использовали Метод чтобы добавить к новым постам имя автора, id автора, а также текущее время и дату на сервере.

    Однако, если Meteor выполнял бы Методы самым примитивным способом, у нас появились бы проблемы. Представьте себе следующую цепочку событий (временные интервалы здесь просто случайные цифры в целях иллюстрации):

    • +0ms: Пользователь жмет кнопку Submit и браузер вызывает Метод.
    • +200ms: Сервер вносит изменения в базу данных Mongo.
    • +500ms: Клиент получает измененные данные и обновляет интерфейс чтобы их отобразить

    Если бы Meteor работал именно так, у нас была бы задержка между действием пользователя и отражением этого действия приложением (задержка будет более или менее ощутимая в зависимости как далеко вы находитесь от сервера). В современном веб приложении подобное запаздывание совершенно недопустимо!

    Компенсация задержки

    С компенсацией задержки передачи данных
    С компенсацией задержки передачи данных

    Чтобы избежать подобных проблем Meteor использует концепцию под названием Компенсация Задержки (Latency Compensation). Когда мы создали Метод post, мы добавили его в файл в папке collections/. Это означает что он доступен как серверу, так и клиенту - и оба могут запустить Метод одновременно!

    Когда вы вызываете Метод, клиент посылает запрос на сервер. Но он также одновременно симулирует вызов Метода на коллекции клиента. Наша цепочка событий превращается в следующую:

    • +0ms: Пользователь нажимает на кнопку Submit. Браузер вызывает Метод на сервере.
    • +0ms: Клиент симулирует действие Метода на своих локальных коллекциях, и тут же отражает его результат в пользовательском интерфейсе.
    • +200ms: Сервер вносит изменения в базу данных Mongo.
    • +500ms: Клиент получает ответ от сервера с результатом операции. Клиент отменяет симулированные изменения и заменяет их настоящими, пришедшими с сервера (которые, как правило, совпадают с симулированными). Пользовательский интерфейс обновляется чтобы отразить изменения, если они есть.

    В результате пользователь видит изменения мгновенно. Когда приложение получит ответ от сервера несколько мгновений спустя, интерфейс приложения может поменяться (а может и остаться прежним) чтобы отразить истинные изменения. Чтобы эти изменения оставались минимальными, нам нужно как можно лучше симулировать реальные документы.

    Наблюдаем за компенсацией задержки

    Добавим небольшое изменение в Метод post чтобы понаблюдать за действиями клиента и сервера. Для этого мы напишем слегка продвинутый код с помощью npm пакета futures - он позволит нам замедлить создание объектов в Методе.

    Мы воспользуемся свойством isSimulation чтобы узнать у Meteor если метод был вызван как stub. Stub это та самая симуляция Метода на клиенте, которую Meteor запускает параллельно с настоящим Методом на сервере.

    Мы узнаем у Meteor выполняется ли код на клиенте. Если да - добавим строку (client) в конец заголовка нового поста. Если нет, добавим строку (server):

    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;
      }
    });
    
    collections/posts.js

    Внимание: если вы задумались что означает this в this.isSimulation - это так называемый Method invocation object (объект вызова Метода) который дает доступ к разным полезным переменным.

    Подробный разбор пакета Futures выходит за рамки нашей книги. Но если вкратце - мы просто сообщили Meteor выждать 5 секунд перед тем как добавлять новый объект в коллекцию на сервере.

    Мы также совершим перенаправление пользователя на страницу со списком постов:

    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');
      }
    });
    
    client/views/posts/post_submit.js

    Коммит 7-5-1

    Демонстрируем порядок появления постов с помощью метода s…

    Если мы создадим пост, мы наглядно увидим компенсацию задержки в действии. Сначала пост появится со строкой (client) в заголовке (первый пост в списке, с ссылкой на GitHub):

    Наш пост сохранен в коллекции клиента
    Наш пост сохранен в коллекции клиента

    Затем, 5 секунд спустя, он заменяется настоящим документом с сервера:

    Наш пост после ответа сервера
    Наш пост после ответа сервера

    Методы коллекций клиента

    После всего этого можно подумать, что Методы довольно сложны. На самом деле они могут быть очень просты. Мы уже увидели три очень простых Метода для редактирования коллекций - insert, update и remove.

    Когда вы создаете новую коллекцию posts, вы также создаете три Метода: posts/insert, posts/update и posts/delete. Другими словами, когда вы вызываете Posts.insert() у коллекции клиента, вы вызываете Метод с компенсацией задержки, который делает две вещи:

    1. Проверяет есть ли у него возможность редактировать коллекцию вызывая функции allow и deny.
    2. Редактирует локальную коллекцию.

    Методы вызывающие Методы

    Если вы еще не потеряли нить повествования, вероятно вы только что заметили что наш Метод post вызывает другой Метод (posts/insert) когда мы создаем наш пост. Как это работает?

    Когда запускается симуляция (версия Метода на клиенте), мы также запускаем симуляцию метода insert (таким образом добавляя новый объект в коллекцию на клиенте). Но мы не вызываем настоящий, серверный insert - мы ожидаем что серверная версия метода post сделает это.

    Последовательно, когда серверная версия метода post вызывает insert, нам не нужно беспокоиться насчет симуляции, и объект успешно создается в главной базе данных.