/**
* If enabled (on local or development builds) will schedule incoming request to allow
* for specific requests to run in isolation (without any other request being handled
* simultaneously). This is useful for debugMode, where we want to only capture error and
* debug logs related to this request from stdout en stderror.
*/
class RequestScheduler {
enabled = false;
/**
* Promises of requests that are currently running
* @type {Array}
*/
pendingRequests = [];
/**
* Callbacks of requests that have been queued because the scheduler was locked
* @type {Array}
*/
queue = [];
/**
* If true, all incoming requests will be put into the queue instead of executed
* @type {boolean}
*/
locked = false;
enable() {
this.enabled = true;
}
/**
* Whenever a locked request finishes, will flush everything from the queue and call
* handleRequest on them
*/
flushQueue() {
const queue = [...this.queue];
this.queue.length = 0;
queue.forEach(({ callback, isolate }) => this.scheduleRequest(callback, isolate));
}
/**
* Handles an incoming request.
* @param callback {function} A callback to handle a request which returns a Promise that resolves
* or rejects when the request is complete
* @param [isolate=false] {boolean} If true, will execute the request in isolation (while no
* other requests are running).
*/
scheduleRequest(callback, isolate = false) {
if (!this.enabled) {
callback();
return;
}
if (this.locked) {
this.queue.push({ callback, isolate });
return;
}
if (isolate) {
if (this.pendingRequests.length) {
// wait for all the pending requests to finish then try again
Promise.all(this.pendingRequests).then(() => this.scheduleRequest(callback, true));
return;
}
// lock, run the callback, and unlock when complete
this.locked = true;
callback()
.then(() => {
this.locked = false;
this.flushQueue();
})
.catch(e => {
// unlock scheduler regardless of error
this.locked = false;
this.flushQueue();
throw e;
});
return;
}
const request = callback();
this.pendingRequests.push(request);
request
.then(() => {
this.pendingRequests.splice(this.pendingRequests.indexOf(request), 1);
})
.catch(e => {
// remove from pending requests regardless of error
this.pendingRequests.splice(this.pendingRequests.indexOf(request), 1);
throw e;
});
}
}
export default RequestScheduler;