@itwin/ecschema-metadata
Version:
ECObjects core concepts in typescript
90 lines • 4.09 kB
JavaScript
/*---------------------------------------------------------------------------------------------
* 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
*/
/**
* Similar to a normal Promise, a DelayedPromise represents the eventual completion (or failure)
* and resulting value of an asynchronous operation ***that has not yet started***.
*
* The asynchronous operation behind a DelayedPromise will start when any of the following occurs:
* - The DelayedPromise is `await`ed.
* - A callback is attached via `.then()` or `.catch(() => { })`.
* - The asynchronous operation is explicitly started via `.start()`
*
* Just as normal Promises will never return to their pending state once fulfilled or rejected,
* a DelayedPromise will never re-execute its asynchronous operation more than **once**.
*
* Ultimately, a DelayedPromise is nothing more than some syntactic sugar that allows you to
* represent an (asynchronously) lazily-loaded value as an instance property instead of a method.
* You could also accomplish something similar by defining an async function as a property getter.
* However, since a property defined as a DelayedPromise will not start simply by being accessed,
* additional (non-lazily-loaded) "nested" properties can be added.
*
* [!alert text="*Remember:* Unlike regular Promises in JavaScript, DelayedPromises represent processes that **may not** already be happening." kind="warning"]
* @internal
*/
export class DelayedPromise {
/**
* Constructs a DelayedPromise object.
* @param startCallback The asynchronous callback to execute when this DelayedPromise should be "started".
*/
constructor(startCallback) {
let pending;
this.start = async () => {
pending = pending || startCallback();
return pending;
};
}
// We need this in order to fulfill the Promise interface defined in lib.es2015.symbol.wellknown.d.ts
[Symbol.toStringTag] = "Promise";
/**
* Explicitly starts the asynchronous operation behind this DelayedPromise (if it hasn't started already).
*/
start;
/**
* Attaches callbacks for the resolution and/or rejection of the Promise.
* @param onfulfilled The callback to execute when the Promise is resolved.
* @param onrejected The callback to execute when the Promise is rejected.
* @return A Promise for the completion of which ever callback is executed.
*/
async then(onfulfilled, onrejected) {
return this.start().then(onfulfilled, onrejected);
}
/**
* Attaches a callback for only the rejection of the Promise.
* @param onrejected The callback to execute when the Promise is rejected.
* @return A Promise for the completion of the callback.
*/
async catch(onrejected) {
return this.start().catch(onrejected);
}
/**
* Attaches a callback for only the finally clause of the Promise.
* @param onrejected The callback to execute when the Promise is finalized.
* @return A Promise for the completion of the callback.
*/
async finally(onFinally) {
return this.start().finally(onFinally);
}
}
// Because the property getters that wrap `props` are dynamically added, TypeScript isn't aware of them.
// So by defining this as a class _expression_, we can cast the constructed type to Readonly<TProps> & DelayedPromise<TPayload>
/**
* @internal
*/
// eslint-disable-next-line @typescript-eslint/naming-convention
export const DelayedPromiseWithProps = (class extends DelayedPromise {
constructor(props, cb) {
super(cb);
const handler = {
get: (target, name) => {
return (name in this) ? this[name] : target[name];
},
};
return new Proxy(props, handler);
}
});
//# sourceMappingURL=DelayedPromise.js.map