I'm updating some old code (not mine originally) that uses Bluebird promises. I'd rather use native ES6 Promise
s instead, but the old code uses a function Promise
doesn't have, one to check if promises have been settled.
This is related to a similar question (Is there a way to tell if an ES6 promise is fulfilled/rejected/resolved?), but the solution given there is very different, so I wanted to know if the following code is a reasonable approach to the problem.
export class QueryablePromise<T> extends Promise<T> {
private _isRejected = false;
private _isResolved = false;
private _isSettled = false;
then<TResult1 = T, TResult2 = never>(
onResolved?: ((value: T) => TResult1 | PromiseLike<TResult1>) | undefined | null,
onRejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null
): Promise<TResult1 | TResult2> {
const newResolved = onResolved && ((value: T): TResult1 | PromiseLike<TResult1> => {
this._isResolved = true;
this._isSettled = true;
return onResolved(value);
});
const newRejected = onRejected && ((reason: any): TResult2 | PromiseLike<TResult2> => {
this._isRejected = true;
this._isSettled = true;
return onRejected(reason);
});
return super.then(newResolved, newRejected);
}
catch<TResult = never>(
onRejected?: ((reason: any) => TResult | PromiseLike<TResult>) | undefined | null
): Promise<T | TResult> {
const newRejected = onRejected && ((reason: any): TResult | PromiseLike<TResult> => {
this._isRejected = true;
this._isSettled = true;
return onRejected(reason);
});
return super.catch(newRejected);
}
finally(
onFinally?: (() => void) | undefined | null
): Promise<T> {
const newFinally = onFinally && ((): void => {
this._isSettled = true;
return onFinally();
});
return super.finally(newFinally);
}
get isRejected(): boolean { return this._isRejected; }
get isResolved(): boolean { return this._isResolved; }
get isSettled(): boolean { return this._isSettled; }
}
Basically I'm wrapping each callback passed to then
, catch
, and finally
in another function which sets the appropriate flags and then calls the original callback. Flags could easily be set redundantly many times this way, but I don't see that as being much of a problem.
I tried to think of a way to solve this problem using the constructor for my promise subclass, and somehow put a wrapper around the original executor argument to intercept invocations of resolve
and reject
, but couldn't quite put my head around a way to implement that type of solution.
I also tried simply adding my own separate then
, catch
, and finally
callbacks in a constructor for this subclass, with each having nothing to do but set my status flags, but oddly enough that resulted in a stack overflow, the constructor being called recursively until it blew up.
CodePudding user response:
This code doesn't seem reliable as presented. You need to remember that Promises resolve automatically when created.
With the code above, this simple test would fail:
const a = new QueryablePromise((res, rej) => {
setTimeout(() => { console.log('resolved'); res()}, 100)
});
setTimeout(() => {
console.log(a.isSettled)
}, 500);
Will output
// resolved
// false
All cases need to be considered, not only when then
, catch
or finally
are called. I would probably override the constructor and wrap the resolve
and reject
functions like this:
class QueryablePromise<T> extends Promise<T> {
private _isRejected: boolean | undefined;
private _isResolved: boolean | undefined;
private _isSettled: boolean | undefined;
constructor (fn: (res: (value: T | PromiseLike<T>) => void, rej: (reason?: any) => void) => void) {
super((resolve, reject) => fn(
value => {
resolve(value)
this._isResolved = true;
this._isSettled = true;
},
reason => {
reject(reason)
this._isRejected = true;
this._isSettled = true;
},
));
this._isRejected = false;
this._isResolved = false;
this._isSettled = false;
}
get isRejected(): boolean { return this._isRejected!; }
get isResolved(): boolean { return this._isResolved!; }
get isSettled(): boolean { return this._isSettled!; }
}