MBOILE-2838 site: Batch WS calls to reduce the number of HTTP requests
parent
76bbc30ed9
commit
21971a9ab4
|
@ -26,7 +26,7 @@ import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
|||
import { CoreTextUtilsProvider } from '@providers/utils/text';
|
||||
import { CoreTimeUtilsProvider } from '@providers/utils/time';
|
||||
import { CoreUrlUtilsProvider } from '@providers/utils/url';
|
||||
import { CoreUtilsProvider } from '@providers/utils/utils';
|
||||
import { CoreUtilsProvider, PromiseDefer } from '@providers/utils/utils';
|
||||
import { CoreConstants } from '@core/constants';
|
||||
import { CoreConfigConstants } from '../configconstants';
|
||||
import { Md5 } from 'ts-md5/dist/md5';
|
||||
|
@ -113,6 +113,17 @@ export interface CoreSiteWSPreSets {
|
|||
* @type {string}
|
||||
*/
|
||||
typeExpected?: string;
|
||||
|
||||
/**
|
||||
* Wehther a pending request in the queue matching the same function and arguments can be reused instead of adding
|
||||
* a new request to the queue. Defaults to true for read requests.
|
||||
*/
|
||||
reusePending?: boolean;
|
||||
|
||||
/**
|
||||
* Whether the request will be be sent immediately as a single request. Defaults to false.
|
||||
*/
|
||||
skipQueue?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -144,6 +155,18 @@ export interface LocalMobileResponse {
|
|||
coreSupported?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Info of a request waiting in the queue.
|
||||
*/
|
||||
interface RequestQueueItem {
|
||||
cacheId: string;
|
||||
method: string;
|
||||
data: any;
|
||||
preSets: CoreSiteWSPreSets;
|
||||
wsPreSets: CoreWSPreSets;
|
||||
deferred: PromiseDefer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Class that represents a site (combination of site + user).
|
||||
* It will have all the site data and provide utility functions regarding a site.
|
||||
|
@ -151,6 +174,9 @@ export interface LocalMobileResponse {
|
|||
* the tables are created in all the sites, not just the current one.
|
||||
*/
|
||||
export class CoreSite {
|
||||
static REQUEST_QUEUE_DELAY = 50; // Maximum number of miliseconds to wait before processing the queue.
|
||||
static REQUEST_QUEUE_LIMIT = 10; // Maximum number of requests allowed in the queue.
|
||||
|
||||
// List of injected services. This class isn't injectable, so it cannot use DI.
|
||||
protected appProvider: CoreAppProvider;
|
||||
protected dbProvider: CoreDbProvider;
|
||||
|
@ -186,6 +212,8 @@ export class CoreSite {
|
|||
protected lastAutoLogin = 0;
|
||||
protected offlineDisabled = false;
|
||||
protected ongoingRequests: { [cacheId: string]: Promise<any> } = {};
|
||||
protected requestQueue: RequestQueueItem[] = [];
|
||||
protected requestQueueTimeout = null;
|
||||
|
||||
/**
|
||||
* Create a site.
|
||||
|
@ -476,6 +504,9 @@ export class CoreSite {
|
|||
if (typeof preSets.saveToCache == 'undefined') {
|
||||
preSets.saveToCache = true;
|
||||
}
|
||||
if (typeof preSets.reusePending == 'undefined') {
|
||||
preSets.reusePending = true;
|
||||
}
|
||||
|
||||
return this.request(method, data, preSets);
|
||||
}
|
||||
|
@ -592,7 +623,7 @@ export class CoreSite {
|
|||
|
||||
const promise = this.getFromCache(method, data, preSets, false, originalData).catch(() => {
|
||||
// Do not pass those options to the core WS factory.
|
||||
return this.wsProvider.call(method, data, wsPreSets).then((response) => {
|
||||
return this.callOrEnqueueRequest(method, data, preSets, wsPreSets).then((response) => {
|
||||
if (preSets.saveToCache) {
|
||||
this.saveToCache(method, data, response, preSets);
|
||||
}
|
||||
|
@ -707,6 +738,146 @@ export class CoreSite {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a request to the queue or calls it immediately when not using the queue.
|
||||
*
|
||||
* @param {string} method The WebService method to be called.
|
||||
* @param {any} data Arguments to pass to the method.
|
||||
* @param {CoreSiteWSPreSets} preSets Extra options related to the site.
|
||||
* @param {CoreWSPreSets} wsPreSets Extra options related to the WS call.
|
||||
* @returns {Promise<any>} Promise resolved with the response when the WS is called.
|
||||
*/
|
||||
protected callOrEnqueueRequest(method: string, data: any, preSets: CoreSiteWSPreSets, wsPreSets: CoreWSPreSets): Promise<any> {
|
||||
if (preSets.skipQueue || !this.wsAvailable('tool_mobile_call_external_functions')) {
|
||||
return this.wsProvider.call(method, data, wsPreSets);
|
||||
}
|
||||
|
||||
const cacheId = this.getCacheId(method, data);
|
||||
|
||||
// Check if there is an identical request waiting in the queue (read requests only by default).
|
||||
if (preSets.reusePending) {
|
||||
const request = this.requestQueue.find((request) => request.cacheId == cacheId);
|
||||
if (request) {
|
||||
return request.deferred.promise;
|
||||
}
|
||||
}
|
||||
|
||||
const request: RequestQueueItem = {
|
||||
cacheId,
|
||||
method,
|
||||
data,
|
||||
preSets,
|
||||
wsPreSets,
|
||||
deferred: {}
|
||||
};
|
||||
|
||||
request.deferred.promise = new Promise((resolve, reject): void => {
|
||||
request.deferred.resolve = resolve;
|
||||
request.deferred.reject = reject;
|
||||
});
|
||||
|
||||
this.requestQueue.push(request);
|
||||
|
||||
if (this.requestQueue.length >= CoreSite.REQUEST_QUEUE_LIMIT) {
|
||||
this.processRequestQueue();
|
||||
} else if (!this.requestQueueTimeout) {
|
||||
this.requestQueueTimeout = setTimeout(this.processRequestQueue.bind(this), CoreSite.REQUEST_QUEUE_DELAY);
|
||||
}
|
||||
|
||||
return request.deferred.promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Call the enqueued web service requests.
|
||||
*/
|
||||
protected processRequestQueue(): void {
|
||||
this.logger.debug(`Processing request queue (${this.requestQueue.length} requests)`);
|
||||
|
||||
// Clear timeout if set.
|
||||
if (this.requestQueueTimeout) {
|
||||
clearTimeout(this.requestQueueTimeout);
|
||||
this.requestQueueTimeout = null;
|
||||
}
|
||||
|
||||
// Extract all requests from the queue.
|
||||
const requests = this.requestQueue;
|
||||
this.requestQueue = [];
|
||||
|
||||
if (requests.length == 1) {
|
||||
// Only one request, do a regular web service call.
|
||||
this.wsProvider.call(requests[0].method, requests[0].data, requests[0].wsPreSets).then((data) => {
|
||||
requests[0].deferred.resolve(data);
|
||||
}).catch((error) => {
|
||||
requests[0].deferred.reject(error);
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const data = {
|
||||
requests: requests.map((request) => {
|
||||
const args = {};
|
||||
const settings = {};
|
||||
|
||||
// Separate WS settings from function arguments.
|
||||
Object.keys(request.data).forEach((key) => {
|
||||
let value = request.data[key];
|
||||
const match = /^moodlews(setting.*)$/.exec(key);
|
||||
if (match) {
|
||||
if (match[1] == 'settingfilter' || match[1] == 'settingfileurl') {
|
||||
// Undo special treatment of these settings in CoreWSProvider.convertValuesToString.
|
||||
value = (value == 'true' ? '1' : '0');
|
||||
}
|
||||
settings[match[1]] = value;
|
||||
} else {
|
||||
args[key] = value;
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
function: request.method,
|
||||
arguments: JSON.stringify(args),
|
||||
...settings
|
||||
};
|
||||
})
|
||||
};
|
||||
|
||||
const wsPresets: CoreWSPreSets = {
|
||||
siteUrl: this.siteUrl,
|
||||
wsToken: this.token,
|
||||
};
|
||||
|
||||
this.wsProvider.call('tool_mobile_call_external_functions', data, wsPresets).then((data) => {
|
||||
if (!data || !data.responses) {
|
||||
return Promise.reject(null);
|
||||
}
|
||||
|
||||
requests.forEach((request, i) => {
|
||||
const response = data.responses[i];
|
||||
|
||||
if (!response) {
|
||||
// Request not executed, enqueue again.
|
||||
this.callOrEnqueueRequest(request.method, request.data, request.preSets, request.wsPreSets);
|
||||
} else if (response.error) {
|
||||
request.deferred.reject(this.textUtils.parseJSON(response.exception));
|
||||
} else {
|
||||
let responseData = this.textUtils.parseJSON(response.data);
|
||||
// Match the behaviour of CoreWSProvider.call when no response is expected.
|
||||
if (!responseData && (typeof wsPresets.responseExpected == 'undefined' || wsPresets.responseExpected)) {
|
||||
responseData = {};
|
||||
}
|
||||
request.deferred.resolve(responseData);
|
||||
}
|
||||
});
|
||||
|
||||
}).catch((error) => {
|
||||
// Error not specific to a single request, reject all promises.
|
||||
requests.forEach((request) => {
|
||||
request.deferred.reject(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a WS is available in this site.
|
||||
*
|
||||
|
|
Loading…
Reference in New Issue