Backbone.js - 在上一次保存之前保存模型时出现问题POST(创建)而不是PUT(更新)请求

时间:2011-05-04 16:35:15

标签: javascript ajax backbone.js

我使用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

  4. 因此,在这种情况下,我们希望有一个POST请求来创建模型,并且一个PUT请求更新模型。

    但是,如果第一个请求仍在处理中,并且我们在上面第3点的代码运行之前没有响应,那么我们实际获得的是两个POST请求,因此创建了两个对象而不是一个。

    所以我的问题是是否有一些最佳实践方法来处理这个问题和Backbone.js?或者,Backbone.js是否应该有一个用于保存操作的排队系统,以便在该对象的上一个请求成功/失败之前不会发送一个请求?或者,或者我应该通过仅发送一个创建请求而不是多个更新请求来构建一些优雅地处理它的东西,可能使用某种类型的限制,或者检查Backbone模型是否在请求的中间并等待该请求是完成。

    您对如何处理此问题的建议表示赞赏。

    我很高兴能够实现某种排队系统,尽管您可能需要忍受我的代码,这些代码不会像现有的代码库一样好!

4 个答案:

答案 0 :(得分:9)

我已经测试并设计了一个补丁解决方案,受到@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. 当仍在执行模型上的更新或创建请求方法时,下一个请求将被放入队列中,以便在调用其中一个错误或成功的回调时进行处理。

    < / LI>
  2. 请求时的属性存储在属性数组中并传递给下一个保存请求。因此,这意味着当服务器使用第一个请求的更新模型进行响应时,排队请求中的更新属性不会丢失。

  3. 我上传了Gist which can be forked here

答案 1 :(得分:5)

一个轻量级的解决方案是修补Backbone.Model.save,所以你只会尝试创建一次模型;应该延迟任何进一步的保存,直到模型具有id。这样的事情应该有用吗?

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 );
    }
};

答案 2 :(得分:1)

我有一些我称之为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;
}
});

(未经测试的代码)

第一次保存时,它会正常保存记录。如果您快速执行新的保存,它将缓冲该调用(将不同的属性和选项合并到一个调用中)。第一次保存成功后,您将继续进行第二次保存。

答案 3 :(得分:0)

作为上述答案的替代方法,您可以通过将backbone.sync方法重载为此模型的同步来实现相同的效果。这样做会强制每次调用等待前一次完成。

另一种选择是在用户提交内容时进行设置并在最后执行一次保存。这样做也可以减少应用程序的请求数量