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

发布于 2024-11-05 08:47:01 字数 1167 浏览 2 评论 0原文

我使用 Backbone.js 开发了一个很好的丰富应用程序界面,用户可以在其中非常快速地添加对象,并且然后只需通过 Tab 键切换到相关字段即可开始更新这些对象的属性。我遇到的问题是,有时用户击败服务器进行初始保存,我们最终保存了两个对象。

如何重现此问题的示例如下:

  1. 用户单击“添加人员”按钮,我们将其添加到 DOM,但尚未保存任何内容,因为我们还没有任何数据。

    person = new Person();

  2. 用户在名称字段中输入一个值,然后按 Tab 键退出(名称字段失去焦点)。这会触发保存,以便我们更新服务器上的模型。由于模型是新的,Backbone.js 会自动向服务器发出 POST(创建)请求。

    person.set({ name: '约翰' });

    person.save(); // 创建新模型

  3. 用户然后快速键入他们已按 Tab 键进入的年龄字段,输入 20 并按 Tab 键转到下一个字段(年龄因此失去焦点)。这再次触发保存,以便我们更新服务器上的模型。

    person.set({ 年龄:20 });

    person.save(); // 更新模型

因此,在这种情况下,我们期望一个 POST 请求来创建模型,一个 PUT 请求来更新模型。

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

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

如果您就如何处理这个问题提出建议,我们将不胜感激。

我很高兴尝试实现某种排队系统,尽管您可能需要忍受我的代码,它的结构不如现有的代码库!

I've developed a nice rich application interface using Backbone.js where users can add objects very quickly, and then start updating properties of those objects by simply tabbing to the relevant fields. The problem I am having is that sometimes the user beats the server to its initial save and we end up saving two objects.

An example of how to recreate this problem is as follows:

  1. User clicks the Add person button, we add this to the DOM but don't save anything yet as we don't have any data yet.

    person = new Person();

  2. User enters a value into the Name field, and tabs out of it (name field loses focus). This triggers a save so that we update the model on the server. As the model is new, Backbone.js will automatically issue a POST (create) request to the server.

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

    person.save(); // create new model

  3. User then very quickly types into the age field they have tabbed into, enters 20 and tabs to the next field (age therefore loses focus). This again triggers a save so that we update the model on the server.

    person.set ({ age: 20 });

    person.save(); // update the model

So we would expect in this scenario one POST request to create the model, and one PUT requests to update the model.

However, if the first request is still being processed and we have not had a response before the code in point 3 above has run, then what we actually get is two POST requests and thus two objects created instead of one.

So my question is whether there is some best practice way of dealing with this problem and Backbone.js? Or, should Backbone.js have a queuing system for save actions so that one request is not sent until the previous request on that object has succeeded/failed? Or, alternatively should I build something to handle this gracefully by either sending only one create request instead of multiple update requests, perhaps use throttling of some sort, or check if the Backbone model is in the middle of a request and wait until that request is completed.

Your advice on how to deal with this issue would be appreciated.

And I'm happy to take a stab at implementing some sort of queuing system, although you may need to put up with my code which just won't be as well formed as the existing code base!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

垂暮老矣 2024-11-12 08:47:01

我已经测试并设计了一个补丁解决方案,受到在此线程中发布的@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,可以在此处分叉

I have tested and devised a patch solution, inspired by both @Paul and @Julien who posted in this thread. Here is the code:

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

The reason this works is as follows:

  1. When an update or create request method on a model is still being executed, the next request is simply put in a queue to be processed when one of the callbacks for error or success are called.

  2. The attributes at the time of the request are stored in an attribute array and passed to the next save request. This therefore means that when the server responds with an updated model for the first request, the updated attributes from the queued request are not lost.

I have uploaded a Gist which can be forked here.

無心 2024-11-12 08:47:01

一个轻量级的解决方案是对 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 );
    }
};

A light-weight solution would be to monkey-patch Backbone.Model.save, so you'll only try to create the model once; any further saves should be deferred until the model has an id. Something like this should work?

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 );
    }
};
浮世清欢 2024-11-12 08:47:01

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

您可以将其用作骨干模型。但它会触发 save 和 save:complete。您可以稍微增强一下:(

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

未经测试的代码)

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

I have some code I call 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;
}
});

You can use it as a backbone model. But it will trigger save and save:complete. You can boost this a little:

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

(untested code)

Upon the first save call it will save the record normally. If you quickly do a new save it will buffer that call (merging the different attributes and options into a single call). Once the first save succeed, you go forward with the second save.

谢绝鈎搭 2024-11-12 08:47:01

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

另一种选择是在用户归档文件时只进行设置,并在最后进行一次保存。这也减少了应用程序发出的请求数量

As an alternative to the above answer, you could achieve the same affect by overloading the backbone.sync method to be synchronous for this model. Doing so would force each call to wait for the previous to finish.

Another option would be to just do the sets when the user is filing things out and do one save at the end. That well also reduce the amount of requests the app makes as well

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文