Backbone.js - проблема при сохранении модели перед предыдущими проблемами сохранения POST (создание) вместо запроса PUT (update)
Я разработал приятный богатый интерфейс приложения, используя Backbone.js, где пользователи могут быстро добавлять объекты, а затем начинают обновлять свойства этих объектов, просто перейдя в соответствующие поля. Проблема, с которой я сталкиваюсь, заключается в том, что иногда пользователь бьет сервер до его первоначального сохранения, и мы в конечном итоге сохраняем два объекта.
Пример воссоздания этой проблемы выглядит следующим образом:
-
Пользователь нажимает кнопку "Добавить человека", мы добавляем это в DOM, но ничего не сохраняем, поскольку у нас пока нет данных.
person = new Person();
-
Пользователь вводит значение в поле "Имя" и выходит из него (поле имени теряет фокус). Это вызывает сохранение, так что мы обновляем модель на сервере. Поскольку модель является новой, Backbone.js автоматически выдаст запрос POST (create) на сервер.
person.set ({ name: 'John' });
person.save(); // create new model
-
Затем пользователь очень быстро вводит в поле возраста, в которое они вошли, вводит 20 и вкладки в следующее поле (возраст, следовательно, теряет фокус). Это снова вызывает сохранение, так что мы обновляем модель на сервере.
person.set ({ age: 20 });
person.save(); // update the model
Таким образом, мы ожидаем в этом случае один запрос POST для создания модели, а один PUT-запрос для обновления модели.
Однако, если первый запрос все еще обрабатывается и у нас не было ответа до того, как был запущен код в пункте 3 выше, тогда мы фактически получаем два запроса POST и, следовательно, два объекта, созданные вместо одного.
Итак, мой вопрос заключается в том, есть ли какой-то лучший способ решения этой проблемы и Backbone.js? Или, если у Backbone.js есть система очередей для сохранения действий, чтобы один запрос не отправлялся до тех пор, пока предыдущий запрос на этот объект не завершился или не удался? Или, в качестве альтернативы, я должен построить что-то, чтобы обработать это изящно, отправив только один запрос на создание вместо нескольких запросов на обновление, возможно, используя дросселирование какого-либо типа или проверить, находится ли модель Backbone в середине запроса и ждать, пока этот запрос не будет завершено.
Ваш совет о том, как справиться с этой проблемой, будет оценен.
И я рад принять удар по внедрению какой-то системы очередей, хотя вам, возможно, придется мириться с моим кодом, который просто не будет так хорошо сформирован, как существующая база кода!
Ответы
Ответ 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;
}
}
})();
Причина этого заключается в следующем:
-
Когда метод обновления или создания запроса на модели все еще выполняется, следующий запрос просто помещается в очередь, подлежащую обработке, когда вызывается один из обратных вызовов для ошибки или успеха.
-
Атрибуты во время запроса хранятся в массиве атрибутов и передаются следующему запросу сохранения. Это означает, что когда сервер отвечает обновленной моделью для первого запроса, обновленные атрибуты из запрошенного запроса не теряются.
Я загрузил Gist, который можно развернуть здесь.
Ответ 2
Маловероятное решение было бы для обезьяны-патча 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 );
}
};
Ответ 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;
}
});
(непроверенный код)
При первом вызове сохранения он сохранит запись в обычном режиме. Если вы быстро выполните новое сохранение, он будет буферизовать этот вызов (слияние разных атрибутов и опций в один вызов). Как только первое сохранение будет успешным, вы продолжите второе сохранение.
Ответ 4
В качестве альтернативы приведенному выше ответу вы можете добиться такого же эффекта, перегрузив метод backbone.sync синхронным для этой модели. Это заставит каждый вызов дождаться окончания предыдущего.
Другим вариантом было бы просто делать наборы, когда пользователь записывает информацию и сохраняет ее в конце. Это также уменьшает количество запросов, которые приложение также делает