enum PromiseStatus {
Pending = 'pending',
Fulfilled = 'fulfilled',
Rejected = 'rejected',
}
interface IExecutor<T> {
(resolve: (value?: T | PromiseLike<T>) => void, reject: (reason?: any) => void): void
}
interface Thenable<T> {
then<TResult1 = T, TResult2 = never>(
onFulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | null,
onRejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null
): Promise<TResult1 | TResult2>
}
class Promise<T> implements Thenable<T> {
private status: PromiseStatus = PromiseStatus.Pending
private value: T | undefined
private reason: any | undefined
private onFulfilledCallbacks: ((value: T) => void)[] = []
private onRejectedCallbacks: ((reason: any) => void)[] = []
constructor(executor: IExecutor<T>) {
try {
executor(this.resolve.bind(this), this.reject.bind(this))
} catch (error) {
this.reject(error)
}
}
private resolve(value?: T | PromiseLike<T>): void {
if (this.status === PromiseStatus.Pending) {
if (value instanceof Promise) {
value.then(this.resolve.bind(this), this.reject.bind(this))
} else {
this.status = PromiseStatus.Fulfilled
this.value = value
this.onFulfilledCallbacks.forEach((callback) => callback(this.value as T))
}
}
}
private reject(reason?: any): void {
if (this.status === PromiseStatus.Pending) {
this.status = PromiseStatus.Rejected
this.reason = reason
this.onRejectedCallbacks.forEach((callback) => callback(this.reason))
}
}
public then<TResult = T, TErrResult = never>(
onFulfilled?: ((value: T) => TResult | PromiseLike<TResult>) | null,
onRejected?: ((reason: any) => TErrResult | PromiseLike<TErrResult>) | null
): Promise<TResult | TErrResult> {
const promise = new Promise<TResult | TErrResult>((resolve, reject) => {
if (this.status === PromiseStatus.Fulfilled) {
try {
const result = onFulfilled
? onFulfilled(this.value as T)
: (this.value as unknown as TResult)
promiseResolutionProcedure(promise, result, resolve, reject)
} catch (error) {
reject(error)
}
} else if (this.status === PromiseStatus.Rejected) {
try {
const result = onRejected
? onRejected(this.reason)
: (this.reason as unknown as TErrResult)
promiseResolutionProcedure(promise, result, resolve, reject)
} catch (error) {
reject(error)
}
} else {
this.onFulfilledCallbacks.push((value: T) => {
try {
const result = onFulfilled
? onFulfilled(value)
: (value as unknown as TResult)
promiseResolutionProcedure(promise, result, resolve, reject)
} catch (error) {
reject(error)
}
})
this.onRejectedCallbacks.push((reason: any) => {
try {
const result = onRejected
? onRejected(reason)
: (reason as unknown as TErrResult)
promiseResolutionProcedure(promise, result, resolve, reject)
} catch (error) {
reject(error)
}
})
}
})
return promise
}
public catch<TResult = never>(
onRejected?: ((reason: any) => TResult | PromiseLike<TResult>) | null
): Promise<T | TResult> {
return this.then(undefined, onRejected)
}
public finally(onFinally?: (() => void) | null): Promise<T> {
return this.then<T>(
(value) => Promise.resolve(onFinally && onFinally()).then(() => value),
(reason) =>
Promise.resolve(onFinally && onFinally()).then(() => {
throw reason
})
)
}
public static race<T>(promises: Iterable<T | PromiseLike<T>>): Promise<T> {
return new Promise((resolve, reject) => {
for (const promise of promises) {
Promise.resolve(promise).then(resolve, reject)
}
})
}
public static all<T>(promises: Iterable<T | PromiseLike<T>>): Promise<T[]> {
const result: T[] = []
const promiseCount = Array.isArray(promises)
? promises.length
: Object.keys(promises).length
const isIterable = typeof promises[Symbol.iterator] === 'function'
if (promiseCount === 0) {
return Promise.resolve(result)
}
let resolveCount = 0
return new Promise((resolve, reject) => {
function handleResolved(index: number, value: T) {
result[index] = value
resolveCount++
if (resolveCount === promiseCount) {
resolve(result)
}
}
function handleRejected(reason: any) {
reject(reason)
}
let index = 0
for (const promise of isIterable
? (promises as Iterable<T | PromiseLike<T>>)
: Object.values(promises)) {
Promise.resolve(promise).then((value: T) => {
handleResolved(index, value)
}, handleRejected)
index++
}
})
}
}
function promiseResolutionProcedure<T>(
promise: Promise<T>,
x: unknown,
resolve: (value?: T | PromiseLike<T>) => void,
reject: (reason?: any) => void
): void {
if (promise === x) {
reject(new TypeError('Chaining cycle detected for promise'))
} else if (x instanceof Promise) {
x.then(resolve, reject)
} else if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
try {
const then = (x as { then: Function }).then
if (typeof then === 'function') {
let called = false
try {
then.call(
x,
(y: unknown) => {
if (!called) {
called = true
promiseResolutionProcedure(promise, y, resolve, reject)
}
},
(r: any) => {
if (!called) {
called = true
reject(r)
}
}
)
} catch (error) {
if (!called) {
called = true
reject(error)
}
}
} else {
resolve(x as T)
}
} catch (error) {
reject(error)
}
} else {
resolve(x as T)
}
}