@itwin/core-bentley
Version:
Bentley JavaScript core components
104 lines • 5.24 kB
JavaScript
"use strict";
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
/** @packageDocumentation
* @module Utils
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.OneAtATimeAction = exports.AbandonedError = void 0;
const BentleyError_1 = require("./BentleyError");
/** @beta */
class AbandonedError extends Error {
}
exports.AbandonedError = AbandonedError;
/**
* An object that returns a Promise when you call [[init]], but supplies a way to abandon the promise if it is no longer relevant.
* When you call abandon, the promise will be rejected. You must supply a [[run]] method to the constructor that
* creates the real Promise for the underlying action. Notice that to use this class there are really two
* Promises involved that are chained together. That makes this class less efficient than just using a Promise directly.
*/
class PromiseWithAbandon {
_run;
_args;
/** Method to abandon the Promise created by [[init]] while it is outstanding. The promise will be rejected. */
abandon;
_resolve;
/** Create a PromiseWithAbandon. After this call you must call [[init]] to create the underlying Promise.
* @param _run The method that creates the underlying Promise.
* @param _args An array of args to be passed to run when [[start]] is called.
*/
constructor(_run, _args) {
this._run = _run;
this._args = _args;
}
/** Create a Promise that is chained to the underlying Promise, but is connected to the abandon method. */
async init(msg) {
return new Promise((resolve, reject) => {
this.abandon = (message) => reject(new AbandonedError(message ?? msg));
this._resolve = resolve;
});
}
/** Call the [[run]] method supplied to the ctor to start the underlying Promise. */
async start() {
try {
this._resolve(await this._run(...this._args));
}
catch (err) {
this.abandon(BentleyError_1.BentleyError.getErrorMessage(err)); // turn all errors from execution into abandoned errors, but keep the message
}
}
}
/**
* Orchestrator of a one-at-a-time activity. This concept is useful only for *replaceable* operations (that is, operations where subsequent requests replace and obviate
* the need for previous requests. E.g. over slow HTTP connections, without this class, the stream of requests can overwhelm the connection, and cause the HTTP
* request queue to grow such that the delay to service new requests is unbounded.
*
* With this class, we issue the initial request immediately. When the second request arrives before the first one completes, it becomes *pending*. If subsequent
* requests arrive with a pending request, the current pending request is *abandoned* (its Promise is rejected) and the new request becomes pending.
* When the active request completes, the pending request (if present) is started. In this manner there will only ever be one outstanding HTTP request for this type
* of operation, but the first and last request will always eventually complete.
* @beta
*/
class OneAtATimeAction {
_active;
_pending;
_run;
msg;
/** Ctor for OneAtATimePromise.
* @param run The method that performs an action that creates the Promise.
*/
constructor(run, msg = "abandoned") {
this._run = run;
this.msg = msg;
}
/** Add a new request to this OneAtATimePromise. The request will only run when no other outstanding requests are active.
* @note Callers of this method *must* handle AbandonedError rejections.
*/
async request(...args) {
const entry = new PromiseWithAbandon(this._run, args); // create an "abandon-able promise" object
const promise = entry.init(this.msg); // create the Promise from PromiseWithAbandon. Note: this must be called before we call start.
if (this._active !== undefined) { // is there an active request?
if (this._pending) // yes. If there is also a pending request, this one replaces it and previous one is abandoned
this._pending.abandon(); // rejects previous call to this method, throwing AbandonedError.
this._pending = entry;
}
else {
this._active = entry; // this is the first request, start it.
entry.start(); // eslint-disable-line @typescript-eslint/no-floating-promises
}
try {
return await promise;
}
finally {
// do all of this whether promise was fulfilled or rejected
this._active = this._pending; // see if there's a pending request waiting
this._pending = undefined; // clear pending
if (this._active)
this._active.start(); // eslint-disable-line @typescript-eslint/no-floating-promises
}
}
}
exports.OneAtATimeAction = OneAtATimeAction;
//# sourceMappingURL=OneAtATimeAction.js.map