So, this is yet another Node.js Promise implementation, but this is written in TypeScript, and it strives for type safety and clarity. The full project is located here: https://github.com/llint/PromiseX, feel free to download it and test it out.
Please give your valuable comments regarding any missing bits or errors, as well as any potential improvements (I'm only 5 days into Node/TypeScript/JavaScript), thanks in advance.
function isPromiseLike<T>(x: any): x is PromiseLike<T> {
return x != null && (<PromiseLike<T>>x).then != undefined;
}
function isPromiseX<T>(x: PromiseLike<T>): x is PromiseX<T> {
return x != null && (<PromiseX<T>>x).type == PromiseX.symbolPromiseX;
}
enum State { Pending, Fulfilled, Rejected }
export class PromiseX<T = any> implements PromiseLike<T> {
static symbolPromiseX: symbol = Symbol('PromiseX');
readonly type: symbol = PromiseX.symbolPromiseX;
private _state: State = State.Pending;
private _result: T;
private _reason: any;
private _continuations: [(x: T) => void, (e: any) => void][] = [];
constructor(executor?: (resolve: (value?: T | PromiseLike<T>) => void, reject: (reason?: any) => void) => void) {
if (executor) {
executor(result => this.setResult(result), error => this.setError(error));
}
}
// if 'result' is 'undefined', it'll be passed along
// Note that there is a potential race if the current state is pending, while
// someone calls setResult on two different PromiseLike values - the first one
// gets either fulfilled or rejected would set the current promise in a non-pending state,
// and the second PromiseLike when eventually becomes non-pending would call either setResult or setError
// but find the state becomes non-pending, thus would have no effect!
public setResult(result?: T | PromiseLike<T>): void {
if (this._state == State.Pending) {
if (isPromiseLike(result)) {
result.then(x => this.setResult(x), e => this.setError(e));
} else {
this._state = State.Fulfilled;
this._result = result;
for (let continuation of this._continuations) {
continuation[0](this._result);
}
}
}
}
public setError(reason?: any): void {
if (this._state == State.Pending) {
this._state = State.Rejected;
this._reason = reason;
for (let continuation of this._continuations) {
continuation[1](this._reason);
}
}
}
then<TResult1 = T, TResult2 = never>(onFulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | undefined | null, onRejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null): PromiseX<TResult1 | TResult2> {
switch (this._state) {
case State.Fulfilled: {
let r : TResult1 | PromiseLike<TResult1>;
if (onFulfilled) {
try {
r = onFulfilled(this._result);
} catch (e) {
let p = new PromiseX<never>();
p.setError(e);
return p;
}
}
if (isPromiseLike(r) && isPromiseX(r)) {
return r;
}
let p = new PromiseX<TResult1>();
p.setResult(r);
return p;
}
case State.Rejected: {
if (onRejected) {
let r : TResult2 | PromiseLike<TResult2>;
try {
r = onRejected(this._reason);
} catch (e) {
let p = new PromiseX<never>();
p.setError(e);
return p;
}
if (isPromiseLike(r) && isPromiseX(r)) {
return r;
}
let p = new PromiseX<TResult2>();
p.setResult(r); // NB: the error has been handled, we pass along the handled result
return p;
}
let p = new PromiseX<TResult2>();
p.setError(this._reason); // NB: the error is not handled, mark the returned promise to be the same error, so it can be passed along until handled
return p;
}
case State.Pending: {
let p = new PromiseX<TResult1 | TResult2>();
this._continuations.push([
result => {
let r : TResult1 | PromiseLike<TResult1>;
if (onFulfilled) {
try {
r = onFulfilled(result);
} catch (e) {
p.setError(e);
return;
}
}
p.setResult(r);
},
reason => {
if (onRejected) {
let r : TResult2 | PromiseLike<TResult2>;
try {
r = onRejected(reason);
} catch (e) {
p.setError(e);
return;
}
p.setResult(r);
} else {
p.setError(reason); // NB: error not handled, pass the error along the chain
}
}
]);
return p;
}
}
}
catch<TResult = never>(onRejected?: ((reason: any) => TResult | PromiseLike<TResult>) | undefined | null): PromiseX<T | TResult> {
return this.then(undefined, onRejected);
}
}