【问题标题】:Backbone.js - problem when saving a model before previous save issues POST(create) instead of PUT(update) requestBackbone.js - 在先前保存发出 POST(create) 而不是 PUT(update) 请求之前保存模型时出现问题
【发布时间】:2011-05-04 16:35:14
【问题描述】:

我使用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 模型是否在请求中间并等到该请求是完成。

我们将不胜感激您对如何处理此问题的建议。

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

【问题讨论】:

    标签: javascript ajax backbone.js


    【解决方案1】:

    我已经测试并设计了一个补丁解决方案,灵感来自在此线程中发布的 @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 which can be forked here

    【讨论】:

    【解决方案2】:

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

    【讨论】:

    • 这看起来是一个非常简洁的解决方案。下周我将在开发中实施这个,然后会回复我的反馈。不过很好的解决方案。
    • 不幸的是,由于以下几个原因,这不起作用:this.requests 始终返回 true 并且不会触发链接事件,因此始终会立即触发,不会延迟。此外,模型的属性会在服务器响应时更新,因此直接使用 model.set() 编写的任何属性都会在后续请求中丢失。我将在下面发布一个可行的解决方案。
    • 您是否在 HEAD 版本的 Backbone 上尝试过这个?我忘了提及它,但这应该在保存时返回实际的 XHR 请求对象(而不是模型,在较旧的 Backbone 版本中);它至少不应该返回“真”?
    • 这是最优雅的解决方案。它应该作为拉取请求包含在 Backbone master 中。
    • 这应该被调整:save() 应该(根据 Backbone 文档)在成功时返回 jqXHR 对象 - 所以你应该添加 return this.request 至少在 else 的情况下。
    【解决方案3】:

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

    (未经测试的代码)

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

    【讨论】:

    • @Julien 的回答很棒,但有几个问题。由于 Stackoverflow 中的 cmets 格式很垃圾,请原谅我为以下每个问题添加了许多 cmets:
    • 问题一:代码if (complete) { return success(self, resp); }不应该读成if (complete) { return complete(self, resp); }
    • 问题2:如果将完成的选项回调传递给原始保存函数,那么在执行需求更新代码之前调用return肯定不会调用更新?
    • 第 3 点:我将在接下来的几天内努力集成此代码,并发布代码要点......所以我很快就会回来提供经过测试的代码。跨度>
    • 对于问题 #1。是的,此时不应返回。如果(完成){完成(自我,resp); }
    【解决方案4】:

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

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

    【讨论】:

    • 使 Backbone.sync 同步会显着降低用户对应用程序的体验。该应用程序类似于 Google Docs,因此需要感觉像一个桌面应用程序并在后台持续存在。不幸的是,最后保存对我来说不是一个好选择,因为我担心我会丢失一些更新。该应用程序类似于电子表格,一旦用户离开该单元格,我认为我们需要更新模型(实际上是行)。
    • 我并不是说要使整个应用程序同步,只是覆盖此模型的同步。即使使用排队系统,如果您有 7 或 8 个保存排队并且用户导航离开屏幕,这些保存就会丢失。在这种情况下,您仍然必须以某种方式将用户锁定在屏幕上,直到保存完成。排队系统修复了你的竞争条件,但也提出了这个问题。为什么要构建自己的队列系统,将 ajax 请求作为同步而不是异步发送时达到相同的结果?
    猜你喜欢
    • 2011-09-17
    • 1970-01-01
    • 1970-01-01
    • 2012-03-27
    • 1970-01-01
    • 2023-01-23
    • 1970-01-01
    • 2021-09-06
    • 1970-01-01
    相关资源
    最近更新 更多