Backbone.js - проблема при сохранении модели перед предыдущим сохранением. Выдает POST (создание) вместо запроса PUT (обновление) - PullRequest
15 голосов
/ 04 мая 2011

Я разработал хороший многофункциональный интерфейс приложения, используя Backbone.js , где пользователи могут очень быстро добавлять объекты, а затем начать обновлять свойства этих объектов, просто вкладывая их в соответствующие поля. Проблема, с которой я сталкиваюсь, заключается в том, что иногда пользователь бьет по серверу до первоначального сохранения, и в итоге мы сохраняем два объекта.

Пример того, как воссоздать эту проблему, выглядит следующим образом:

  1. Пользователь нажимает кнопку «Добавить человека», мы добавляем ее в DOM, но пока ничего не сохраняем, поскольку у нас еще нет данных.

    person = new Person();

  2. Пользователь вводит значение в поле Имя и выделяет его (поле имени теряет фокус). Это инициирует сохранение, чтобы мы обновили модель на сервере. Поскольку модель новая, Backbone.js автоматически отправит POST (создать) запрос на сервер.

    person.set ({ name: 'John' });

    person.save(); // create new model

  3. Затем пользователь очень быстро вводит в поле возраста, в которое он вкладывал, вводит 20 и вкладки в следующее поле (поэтому возраст теряет фокус). Это снова вызывает сохранение, чтобы мы обновили модель на сервере.

    person.set ({ age: 20 });

    person.save(); // update the model

Таким образом, в этом сценарии мы ожидаем один запрос POST для создания модели и один запрос PUT для обновления модели.

Однако, если первый запрос все еще обрабатывается, и у нас не было ответа до выполнения кода в пункте 3 выше, то мы фактически получаем два запроса POST и, следовательно, два объекта, созданных вместо одного.

Итак, мой вопрос: есть ли какой-нибудь наилучший способ решения этой проблемы и Backbone.js? Или, должен ли Backbone.js иметь систему очередей для действий сохранения, чтобы один запрос не отправлялся до тех пор, пока предыдущий запрос для этого объекта не будет выполнен успешно или не выполнен? Или, в качестве альтернативы, я должен построить что-то для изящной обработки, отправив только один запрос на создание вместо нескольких запросов на обновление, возможно, использовать какое-либо регулирование или проверить, находится ли модель Backbone в середине запроса, и подождать, пока этот запрос не будет выполнен. завершено.

Буду признателен за советы по решению этой проблемы.

И я рад попытаться реализовать какую-то систему очередей, хотя вам, возможно, придется смириться с моим кодом, который просто не будет так хорошо сформирован, как существующая база кода!

Ответы [ 4 ]

9 голосов
/ 21 июня 2011

Я протестировал и разработал патч-решение, вдохновленное @Paul и @Julien, которые разместили в этой теме. Вот код:

(function() {
  function proxyAjaxEvent(event, options, dit) {
    var eventCallback = options[event];
    options[event] = function() {
      // check if callback for event exists and if so pass on request
      if (eventCallback) { eventCallback(arguments) }
      dit.processQueue(); // move onto next save request in the queue
    }
  }
  Backbone.Model.prototype._save = Backbone.Model.prototype.save;
  Backbone.Model.prototype.save = function( attrs, options ) {
    if (!options) { options = {}; }
    if (this.saving) {
      this.saveQueue = this.saveQueue || new Array();
      this.saveQueue.push({ attrs: _.extend({}, this.attributes, attrs), options: options });
    } else {
      this.saving = true;
      proxyAjaxEvent('success', options, this);
      proxyAjaxEvent('error', options, this);
      Backbone.Model.prototype._save.call( this, attrs, options );
    }
  }
  Backbone.Model.prototype.processQueue = function() {
    if (this.saveQueue && this.saveQueue.length) {
      var saveArgs = this.saveQueue.shift();
      proxyAjaxEvent('success', saveArgs.options, this);
      proxyAjaxEvent('error', saveArgs.options, this);
      Backbone.Model.prototype._save.call( this, saveArgs.attrs, saveArgs.options );
    } else {
      this.saving = false;
    }
  }
})();

Причина, по которой это работает, заключается в следующем:

  1. Когда метод запроса обновления или создания в модели все еще выполняется, следующий запрос просто помещается в очередь для обработки при вызове одного из обратных вызовов для ошибки или успеха.

  2. Атрибуты во время запроса сохраняются в массиве атрибутов и передаются следующему запросу сохранения. Следовательно, это означает, что когда сервер отвечает обновленной моделью на первый запрос, обновленные атрибуты из поставленного в очередь запроса не теряются.

Я загрузил Gist, который можно разложить здесь .

5 голосов
/ 25 мая 2011

Облегченным решением было бы создать Monkey-patch Backbone.Model.save, поэтому вы попытаетесь создать модель только один раз;дальнейшее сохранение должно быть отложено до тех пор, пока модель не получит идентификатор.Как-то так должно работать?

Backbone.Model.prototype._save = Backbone.Model.prototype.save;
Backbone.Model.prototype.save = function( attrs, options ) {
    if ( this.isNew() && this.request ) {
        var dit = this, args = arguments;
        $.when( this.request ).always( function() {
            Backbone.Model.prototype._save.apply( dit, args );
        } );
    }
    else {
        this.request = Backbone.Model.prototype._save.apply( this, arguments );
    }
};
1 голос
/ 06 мая 2011

У меня есть код, который я называю EventedModel:

EventedModel = Backbone.Model.extend({
save: function(attrs, options) {
  var complete, self, success, value;
  self = this;
  options || (options = {});
  success = options.success;
  options.success = function(resp) {
    self.trigger("save:success", self);
    if (success) {
      return success(self, resp);
    }
  };
  complete = options.complete;
  options.complete = function(resp) {
    self.trigger("save:complete", self);
    if (complete) {
      return complete(self, resp);
    }
  };
  this.trigger("save", this);
  value = Backbone.Model.prototype.save.call(this, attrs, options);
  return value;
}
});

Вы можете использовать его в качестве базовой модели. Но это вызовет сохранение и сохранение: завершено. Вы можете немного увеличить это:

EventedSynchroneModel = Backbone.Model.extend({
save: function(attrs, options) {
  var complete, self, success, value;
  if(this.saving){
    if(this.needsUpdate){
      this.needsUpdate = {
         attrs: _.extend(this.needsUpdate, attrs),
         options: _.extend(this.needsUpdate, options)};
    }else {
      this.needsUpdate = { attrs: attrs, options: options };
    }
    return;
  }
  self = this;
  options || (options = {});
  success = options.success;
  options.success = function(resp) {
    self.trigger("save:success", self);
    if (success) {
      return success(self, resp);
    }
  };
  complete = options.complete;
  options.complete = function(resp) {
    self.trigger("save:complete", self);
    //call previous callback if any
    if (complete) {
      complete(self, resp);
    }
    this.saving = false;
    if(self.needsUpdate){
      self.save(self.needsUpdate.attrs, self.needsUpdate.options);
      self.needsUpdate = null;
    }
  };
  this.trigger("save", this);
  // we are saving
  this.saving = true;
  value = Backbone.Model.prototype.save.call(this, attrs, options);
  return value;
}
});

(непроверенный код)

При первом вызове сохранения запись будет сохранена в обычном режиме. Если вы быстро сделаете новое сохранение, оно буферизует этот вызов (объединяя различные атрибуты и опции в один вызов). После первого сохранения вы переходите ко второму сохранению.

0 голосов
/ 06 мая 2011

В качестве альтернативы приведенному выше ответу вы могли бы добиться того же эффекта, перегрузив метод backbone.sync, чтобы он был синхронным для этой модели. Это заставит каждый звонок ждать завершения предыдущего.

Другим вариантом было бы просто сделать наборы, когда пользователь вводит данные, и сделать одно сохранение в конце. Это также уменьшит количество запросов, которые делает приложение

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...