[FIX] web: ajax must return a promise with abort method

Remove part in service_mixins who hide this error. Queries must be able
to be canceled for better performance. Also in the case of longpolling,
if the request is not canceled, wait for a server message or the maximum
time (30 sec) to add or delete channels.
This commit is contained in:
Christophe Matthieu
2018-09-10 16:36:43 +02:00
parent 1199451606
commit dabe5bfbae
8 changed files with 96 additions and 25 deletions
@@ -692,6 +692,9 @@ QUnit.module('account', {
}],
payment_aml_ids: [], new_aml_dicts: []}]],
"Should call process_bank_statement_line with ids");
var def = $.Deferred();
def.abort = function () {};
event.data.callback(def);
});
// click on reconcile button
@@ -734,6 +737,9 @@ QUnit.module('account', {
analytic_tag_ids: [[6, null, []]]
}]}]],
"Should call process_bank_statement_line with ids");
var def = $.Deferred();
def.abort = function () {};
event.data.callback(def);
});
// click on validate button
@@ -787,6 +793,9 @@ QUnit.module('account', {
name: 'SAJ/2014/002 and SAJ/2014/003 : Open balance'
}]}]],
"Should call process_bank_statement_line with ids");
var def = $.Deferred();
def.abort = function () {};
event.data.callback(def);
});
// click on validate button
+1 -1
View File
@@ -38,7 +38,7 @@ function genericJsonRpc (fct_name, params, settings, fct) {
});
// FIXME: jsonp?
deferred.abort = function () {
deferred.reject('communication', $.Event(), 'abort', 'abort');
deferred.reject({message: "XmlHttpRequestError abort"}, $.Event('abort'));
if (xhr.abort) {
xhr.abort();
}
@@ -125,8 +125,14 @@ var ServicesMixin = {
*/
_rpc: function (params, options) {
var query = rpc.buildQuery(params);
var def = this.call('ajax', 'rpc', query.route, query.params, options);
return def ? def.promise() : $.Deferred().promise();
var def = this.call('ajax', 'rpc', query.route, query.params, options, this);
if (!def && this.isDestroyed()) {
def = $.Deferred();
def.abort = function () {};
}
var promise = def.promise();
promise.abort = def.abort.bind(def);
return promise;
},
loadFieldView: function (dataset, view_id, view_type, options) {
return this.loadViews(dataset.model, dataset.get_context().eval(), [[view_id, view_type]], options).then(function (result) {
+7 -2
View File
@@ -344,9 +344,11 @@ var Session = core.Class.extend(mixins.EventDispatcherMixin, {
}
};
return deferred.then(function () {
var promise = deferred.then(function () {
if (aborted) {
return $.Deferred().reject('communication', $.Event(), 'abort', 'abort');
var def = $.Deferred().reject({message: "XmlHttpRequestError abort"}, $.Event('abort'));
def.abort = function () {};
return def;
}
// TODO: remove
if (! _.isString(url)) {
@@ -374,6 +376,9 @@ var Session = core.Class.extend(mixins.EventDispatcherMixin, {
xhrDef = fct(url, "call", params, options);
return xhrDef;
});
promise.abort = deferred.abort;
return promise;
},
url: function (path, params) {
params = _.extend(params || {});
@@ -8,17 +8,20 @@ var session = require('web.session');
var AjaxService = AbstractService.extend({
name: 'ajax',
rpc: function (route, args, options, target) {
return $.Deferred(function (def) {
session.rpc(route, args, options).then(function () {
if (!target.isDestroyed()) {
def.resolve.apply(def, arguments);
}
}, function () {
if (!target.isDestroyed()) {
def.reject.apply(def, arguments);
}
});
}).promise();
var def = $.Deferred();
var promise = def.promise();
var xhrDef = session.rpc(route, args, options);
promise.abort = xhrDef.abort.bind(xhrDef);
xhrDef.then(function () {
if (!target.isDestroyed()) {
def.resolve.apply(def, arguments);
}
}).fail(function () {
if (!target.isDestroyed()) {
def.reject.apply(def, arguments);
}
});
return promise;
},
});
+36 -3
View File
@@ -1,10 +1,12 @@
odoo.define('web.widget_tests', function (require) {
"use strict";
var AjaxService = require('web.AjaxService');
var concurrency = require('web.concurrency');
var core = require('web.core');
var QWeb = require('web.QWeb');
var Widget = require('web.Widget');
var testUtils = require('web.test_utils');
QUnit.module('core', {}, function () {
@@ -377,16 +379,47 @@ QUnit.module('core', {}, function () {
});
QUnit.test("calling _rpc on destroyed widgets", function (assert) {
assert.expect(1);
assert.expect(3);
var def;
var parent = new Widget();
testUtils.addMockEnvironment(parent, {
session: {
rpc: function () {
def = $.Deferred();
def.abort = def.reject;
return def;
},
},
services: [AjaxService],
});
var widget = new Widget(parent);
widget._rpc({route: '/a/route'}).then(function () {
assert.ok(true, "The ajax call should be resolve");
});
def.resolve();
def = null;
var widget = new Widget();
widget.destroy();
widget._rpc({route: '/a/route'}).always(function () {
throw Error("Calling _rpc on a destroyed widget should return a " +
"deferred that is never resolved nor rejected");
});
widget.destroy();
def.resolve();
def = null;
widget._rpc({route: '/a/route'}).always(function () {
throw Error("Calling _rpc on a destroyed widget should return a " +
"deferred that is never resolved nor rejected");
});
assert.ok(!def,
"The trigger_up is not performed and the call returns a deferred "+
"never resolved nor rejected");
assert.ok(true,
"there should be no crash when calling _rpc on a destroyed widget");
parent.destroy();
});
QUnit.test('start is not called when widget is destroyed', function (assert) {
+16 -1
View File
@@ -110,7 +110,18 @@ var MockServer = Class.extend({
console.log('%c[rpc] request ' + route, 'color: blue; font-weight: bold;', args);
args = JSON.parse(JSON.stringify(args));
}
return this._performRpc(route, args).then(function (result) {
var def = this._performRpc(route, args);
var abort = def.abort || def.reject;
if (abort) {
abort = abort.bind(def);
} else {
abort = function () {
throw new Error("Can't abort this request");
};
}
def = def.then(function (result) {
var resultString = JSON.stringify(result || false);
if (debug) {
console.log('%c[rpc] response' + route, 'color: blue; font-weight: bold;', JSON.parse(resultString));
@@ -123,6 +134,10 @@ var MockServer = Class.extend({
}
return $.Deferred().reject(errorString, event || $.Event());
});
var promise = def.promise();
promise.abort = abort;
return promise;
},
//--------------------------------------------------------------------------
@@ -407,15 +407,15 @@ function addMockEnvironment(widget, params) {
var services = {ajax: null}; // mocked ajax service already loaded
intercept(widget, 'call_service', function (ev) {
var args, result;
if (ev.data.service === 'ajax') {
if (services[ev.data.service]) {
var service = services[ev.data.service];
args = (ev.data.args || []);
result = service[ev.data.method].apply(service, args);
} else if (ev.data.service === 'ajax') {
// ajax service is already mocked by the server
var route = ev.data.args[0];
args = ev.data.args[1];
result = mockServer.performRpc(route, args);
} else if (services[ev.data.service]) {
var service = services[ev.data.service];
args = (ev.data.args || []);
result = service[ev.data.method].apply(service, args);
}
ev.data.callback(result);
});