/* global FinalizationRegistry */
let gcRegistry;
try {
gcRegistry = new FinalizationRegistry(stack => {
console.error("Unfinished future detected", stack);
});
} catch(e) {/*no-pragma*/}
/**
* A [Promise]{@link external:Promise}-like object that allows for easy external fulfillment.
* Future objects can also be cancelled to indicate to the fulfiller that the Future is no
* longer being used. Modeled after Python's
* [asyncio.Future]{@link https://docs.python.org/3/library/asyncio-future.html}
*
* @extends external:Promise
*/
export class Future extends Promise {
constructor(options={}) {
let _resolve;
let _reject;
super((resolve, reject) => {
_resolve = resolve;
_reject = reject;
});
this._resolve = _resolve;
this._reject = _reject;
this._pending = true;
this._cancelled = false;
this._trackFinalization = options.trackFinalization && gcRegistry;
if (this._trackFinalization) {
gcRegistry.register(this, (new Error()).stack, this);
}
}
// Allow use of then/catch chaining.
static get [Symbol.species]() {
return Promise;
}
get [Symbol.toStringTag]() {
return 'Future';
}
/**
* Cancel the future and run callbacks.
*
* @returns {boolean} {@link true} if {@link Future} was pending, otherwise {@link false}
*/
cancel() {
if (!this._pending) {
return false;
}
this._cancelled = true;
this._setDone();
return true;
}
/**
* Indicates if the Future was cancelled.
*
* @returns {boolean}
*/
cancelled() {
return this._cancelled;
}
/**
* Indicates if the Future is fulfilled.
*
* @returns {boolean}
*/
done() {
return !this._pending;
}
/**
* Return the result of a fulfilled Future. If the Future is not fulfilled
* it will throw an Error.
*
* @returns {*}
*/
result() {
if (this._pending) {
throw new Error('Unfulfilled Awaitable');
}
if (this._error) {
throw this._error;
}
return this._result;
}
/**
* Return the Error of a fulfilled but rejected Future. If the Future is not
* fulfilled it will throw an Error.
*
* @returns {Error}
*/
error() {
if (this._pending) {
throw new Error('Unfulfilled Awaitable');
}
return this._error;
}
/**
* Add a callback that is executed immediately on fulfillment of the Future.
* For some use cases it is not acceptable to let the event loop run other
* tasks before a finalizer of some sort is run. E.g. Lock and Queue.
*
* @param {Function} callback - A callback that is invoked with this Future.
*/
addImmediateCallback(callback) {
if (this._callbacks === undefined) {
this._callbacks = [callback];
} else {
this._callbacks.push(callback);
}
}
/**
* Set the result of a Future and resolve it. The Future will be put into
* the fulfilled state and any functions awaiting the result will be resumed
* on the next event loop tick.
*
* @param {*} result - Any value that should be passed to awaiters.
*/
setResult(result) {
if (!this._pending) {
throw new Error('Already fulfilled');
}
this._result = result;
this._setDone();
this._resolve(result);
}
_setDone() {
this._pending = false;
if (this._trackFinalization) {
gcRegistry.unregister(this);
}
this._runCallbacks();
}
/**
* Set the Error of a Future and reject it. The Future will be put into
* the fulfilled state and any functions awaiting the result will be resumed
* on the next event loop tick.
*
* @param {Error} e - A valid Error that will be thrown to awaiters.
*/
setError(e) {
if (!this._pending) {
throw new Error('Already fulfilled');
}
this._error = e;
this._reject(e);
this._setDone();
}
_runCallbacks() {
if (this._callbacks !== undefined) {
for (const cb of this._callbacks) {
cb(this);
}
}
}
}
/**
* The built in Promise object.
*
* @external Promise
* @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise}
*/