Skip to content

Commit af7b006

Browse files
committed
[ns.Update] Перезапрос моделей в случае их невалидности #464
1 parent 3d54f43 commit af7b006

File tree

4 files changed

+152
-4
lines changed

4 files changed

+152
-4
lines changed

Diff for: src/ns.update.js

+65-3
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,13 @@
4242

4343
this.promise = new Vow.Promise();
4444

45+
/**
46+
* Количество перезапросов из-за невалидных моделей.
47+
* @type {number}
48+
* @private
49+
*/
50+
this._restartCount = 1;
51+
4552
options = options || {};
4653

4754
/**
@@ -85,6 +92,13 @@
8592
*/
8693
ns.Update.prototype._EVENTS_ORDER = ['ns-view-hide', 'ns-view-htmldestroy', 'ns-view-htmlinit', 'ns-view-async', 'ns-view-show', 'ns-view-touch'];
8794

95+
/**
96+
* Лимит на перезапрос моделей.
97+
* @type {number}
98+
* @constant
99+
*/
100+
ns.Update.prototype.RESTART_LIMIT = 2;
101+
88102
/**
89103
* Регистрирует указанное событие, добавляя к нему признаки ns.update
90104
* @private
@@ -147,7 +161,7 @@
147161
};
148162

149163
if (ns.Update.handleError(error, this)) {
150-
return [].concat(err.invalid, err.valid);
164+
return Vow.resolve([].concat(err.invalid, err.valid));
151165

152166
} else {
153167
return Vow.reject(error);
@@ -201,6 +215,12 @@
201215
this.log('collected incomplete views', views);
202216

203217
var models = views2models(views.sync);
218+
/**
219+
* Массив моделей, которые должны быть валидны перед отрисовкой.
220+
* @private
221+
*/
222+
this._models = models;
223+
204224
this.log('collected needed models', models);
205225
this.switchTimer('collectModels', 'requestSyncModels');
206226

@@ -328,11 +348,53 @@
328348
* потому что у видов будет уже другое состояние, если что-то поменяется между generateHTML и insertNodes
329349
* @private
330350
*/
331-
ns.Update.prototype._updateDOM = function() {
351+
ns.Update.prototype._startUpdateDOM = function() {
332352
if (this._expired()) {
333353
return this._rejectWithStatus(this.STATUS.EXPIRED);
334354
}
335355

356+
// Проверяем валидность моделей перед стартом.
357+
// Из-за асинхронности модели могут оказаться невалидными.
358+
var models = {
359+
valid: [],
360+
invalid: []
361+
};
362+
var modelHasErrors = false;
363+
for (var i = 0, j = this._models.length; i < j; i++) {
364+
var model = this._models[i];
365+
if (model.isValid()) {
366+
models.valid.push(model);
367+
368+
} else {
369+
models.invalid.push(model);
370+
if (model.status === model.STATUS.ERROR) {
371+
modelHasErrors = true;
372+
break;
373+
}
374+
}
375+
}
376+
377+
// если все хорошо, то переходим к обновлению DOM
378+
if (models.invalid.length === 0) {
379+
return this._updateDOM();
380+
}
381+
382+
// если в моделях есть ошибки или мы превысили лимит перезапусков, то фейлимся
383+
if (modelHasErrors || this._restartCount >= this.RESTART_LIMIT) {
384+
// имитируем ошибку "Невалидные модели"
385+
return this._onRequestModelsError(models)
386+
// если ns.Update.handleError решит, что все ок, то запускаем обновление DOM
387+
.then(this._updateDOM, null, this);
388+
389+
} else {
390+
// если модели невалидные, но в них нет ошибок, то пробуем перезапуститься
391+
this._restartCount++;
392+
return this._requestModels(this._models)
393+
.then(this._startUpdateDOM, null, this);
394+
}
395+
};
396+
397+
ns.Update.prototype._updateDOM = function() {
336398
var html = this._renderUpdateTree();
337399
var node = ns.html2node(html || '');
338400
return this._insertNodes(node);
@@ -409,7 +471,7 @@
409471
// начинаем цепочку с промиса, чтобы ловить ошибки в том числе и из _requestAllModels
410472
Vow.invoke(this._requestAllModels.bind(this))
411473
.then(saveAsyncPromises, null, this)
412-
.then(this._updateDOM, null, this)
474+
.then(this._startUpdateDOM, null, this)
413475
.then(fulfillWithAsyncPromises, this._reject, this);
414476

415477
return this.promise;

Diff for: test/spec/ns.update.edge-cases.js

+81
Original file line numberDiff line numberDiff line change
@@ -110,4 +110,85 @@ describe('ns.Update. Синтетические случаи', function() {
110110
expect(ns.request.models).to.have.callCount(0);
111111
});
112112
});
113+
114+
describe('Инвалидация моделей в момент запроса приводит к перезапросу моделей', function() {
115+
116+
beforeEach(function() {
117+
this.sinon.server.autoRespond = true;
118+
this.sinon.server.respond(function(xhr) {
119+
xhr.respond(
120+
200,
121+
{"Content-Type": "application/json"},
122+
JSON.stringify({
123+
models: [
124+
{ data: true }
125+
]
126+
})
127+
);
128+
});
129+
130+
131+
ns.layout.define('page', {
132+
'view1': {
133+
'view2': {}
134+
}
135+
});
136+
137+
ns.Model.define('model1');
138+
ns.Model.define('model2');
139+
140+
ns.View.define('view1', { models: ['model1'] });
141+
ns.View.define('view2', { models: ['model2'] });
142+
143+
ns.Model.get('model1').setData({});
144+
145+
// имитируем, что приходим в _updateDOM с невалидными моделями
146+
var that = this;
147+
this.restartLimit = 1;
148+
var restartCount = 0;
149+
ns.Update.prototype.__stubStartUpdateDOM = ns.Update.prototype._startUpdateDOM;
150+
this.sinon.stub(ns.Update.prototype, '_startUpdateDOM', function() {
151+
if (restartCount < that.restartLimit) {
152+
ns.Model.get('model1').invalidate({});
153+
restartCount++;
154+
}
155+
156+
return this.__stubStartUpdateDOM();
157+
});
158+
159+
this.view = ns.View.create('view1');
160+
this.layout = ns.layout.page('page');
161+
});
162+
163+
afterEach(function() {
164+
delete ns.Update.prototype.__stubStartUpdateDOM;
165+
});
166+
167+
it('должен перезапросить и отрисовать виды', function() {
168+
return new ns.Update(this.view, this.layout).render();
169+
});
170+
171+
it('должен завершить update с ошибкой при превышении лимита', function() {
172+
this.restartLimit = 2;
173+
return new ns.Update(this.view, this.layout)
174+
.render()
175+
.then(function() {
176+
throw new Error('fulfilled');
177+
}, function(err) {
178+
expect(err.error).to.be.equal(ns.U.STATUS.MODELS);
179+
});
180+
});
181+
182+
it('должен отрисовать виды с ошибкой при превышении лимита, если разрешил handleError', function() {
183+
this.restartLimit = 2;
184+
this.sinon.stub(ns.Update, 'handleError').returns(true);
185+
186+
return new ns.Update(this.view, this.layout)
187+
.render()
188+
.then(function() {
189+
expect(this.view.node.outerHTML).to.contain('test ns-view-error-content');
190+
}, null, this);
191+
});
192+
193+
});
113194
});

Diff for: test/tests.yate

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ match .* ns-view-async-content {
2020

2121
match .* ns-view-error-content {
2222
"test ns-view-error-content"
23+
apply . ns-view-content
2324
}
2425

2526
// Это шаблон для проверки того, что yate сбилжен для тестов.

Diff for: yate/noscript.yate

+5-1
Original file line numberDiff line numberDiff line change
@@ -114,4 +114,8 @@ match .* ns-view-async-content {}
114114

115115
// Часть обязательных моделей не удалось получить или для них вернулась ошибка
116116
// @public
117-
match .* ns-view-error-content {}
117+
match .* ns-view-error-content {
118+
// Надо все равно отрисовать дерево,
119+
// чтобы не нарваться на ошибку "Can't find node for <view>"
120+
apply . ns-view-content
121+
}

0 commit comments

Comments
 (0)