Last active
September 3, 2020 06:16
-
-
Save cleoold/7e5e3ba2305e43d8d6b039fcfaa18ad0 to your computer and use it in GitHub Desktop.
basic javascript promise implementation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// implements ultra simple mypromise | |
type Nullable<T> = T | null; | |
function defaultReject(err: any) { | |
throw Error(`uncaught promise rejection: ${err}`); | |
} | |
class MyPromise<T> { | |
private _state: 'pending' | 'resolved' | 'rejected' = 'pending'; | |
private _data: Nullable<T> = null; | |
private _err: Nullable<any> = null; | |
// terminate this promise | |
private onSuccess: Array<(data: T) => void> = []; | |
private onReject: Array<(err: any) => void> = []; | |
public constructor(fn: (resolve: (data: T) => void, reject: (err: any) => void) => void) { | |
try { | |
fn(this.resolveImpl.bind(this), this.rejectImpl.bind(this)); | |
} catch (e) { | |
this.rejectImpl(e); | |
} | |
} | |
public get state() { return this._state; } | |
public get data() { return this._data; } | |
public get error() { return this._err; } | |
private resolveImpl(data: T) { | |
if (this._state !== 'pending') { | |
throw Error('promise cannot be fulfilled twice'); | |
} | |
this._data = data; | |
this._state = 'resolved'; | |
setTimeout(() => this.onSuccess.forEach(func => func(data))); | |
} | |
private rejectImpl(err: any) { | |
if (this._state !== 'pending') { | |
throw Error('promise cannot be fulfilled twice'); | |
} | |
this._err = err; | |
this._state = 'rejected'; | |
setTimeout(() => this.onReject.forEach(func => func(err))); | |
} | |
public done(onSuccess: (data: T) => void, onReject: (_: any) => void = defaultReject) { | |
if (this._state === 'pending') { | |
this.onSuccess.push(onSuccess); | |
this.onReject.push(onReject); | |
// if done is called after promise finishes running | |
} else if (this._state === 'resolved') { | |
setTimeout(() => onSuccess(this._data as T)); | |
} else { | |
setTimeout(() => onReject(this._err)); | |
} | |
return this; | |
} | |
public then<U>(onSuccess: (data: T) => (U | MyPromise<U>), | |
onReject: (_: any) => (any | U | MyPromise<U>) = defaultReject) { | |
return new MyPromise<U>((resolve, reject) => { | |
this.done(data => { | |
try { | |
const ret = onSuccess(data); | |
if (ret instanceof MyPromise) { | |
ret.done(resolve, reject); | |
} else { | |
resolve(ret); | |
} | |
} catch (e) { | |
reject(e); | |
} | |
}, err => { | |
try { | |
const ret = onReject(err); | |
if (ret instanceof MyPromise) { | |
ret.done(resolve, reject); | |
} else { | |
resolve(ret); | |
} | |
} catch (e) { | |
reject(e); | |
} | |
}); | |
}); | |
} | |
// wait for all promises to finish and return all results in an array | |
// errors if any of them errors | |
public static all<U>(promises: Array<MyPromise<U>>) { | |
const res: Array<U> = []; | |
let has_error = false; | |
let count = 0; | |
return new MyPromise<Array<U>>((resolve, reject) => { | |
for (let i = 0; i < promises.length; ++i) { | |
promises[i].then(data => { | |
res[i] = data; | |
if (++count === promises.length) { | |
resolve(res); | |
} | |
}, err => { | |
if (!has_error) { | |
reject(err); | |
} | |
has_error = true; | |
}); | |
} | |
}); | |
} | |
// once one of the promises finishes or errors, return result or error | |
public static race<U>(promises: Array<MyPromise<U>>) { | |
let has_val = false; | |
let has_error = false; | |
return new MyPromise<U>((resolve, reject) => { | |
for (let i = 0; i < promises.length; ++i) { | |
promises[i].then(data => { | |
if (!has_val && !has_error) { | |
resolve(data); | |
} | |
has_val = true; | |
}, err => { | |
if (!has_val && !has_error) { | |
reject(err); | |
} | |
has_error = true; | |
}); | |
} | |
}); | |
} | |
}; | |
// test usage | |
const p1 = new MyPromise<number>((resolve, reject) => { | |
const val = Math.random(); | |
setTimeout(() => (val > 0.5 ? resolve : reject)(val), 50); | |
}).then<void>(data => console.log(`p1 resolved: ${data}`), | |
err => console.log(`p1 rejected: ${err}`)); | |
// may print "p1 resolved: 0.5160408448419662" | |
const p2 = new MyPromise<number>((resolve, reject) => { | |
setTimeout(() => resolve(5), 100); | |
}).then<number>(data => { | |
console.log(`p2 resolved: ${data}`); | |
return 7; | |
}).then<number>(data => new MyPromise<number>((resolve, reject) => { | |
console.log(`p2 resolved: ${data}`); | |
setTimeout(() => resolve(data + 9), 100); | |
})).then<void>(data => { | |
console.log(`p2 resolved: ${data}`); | |
}); | |
// p2 resolved: 5 | |
// p2 resolved: 7 | |
// p2 resolved: 16 | |
const p3 = new MyPromise<string>((resolve, reject) => { | |
new MyPromise<string>((res, rej) => setTimeout(() => res('hahaha'), 500)) | |
.then<void>(resolve); | |
}); | |
p3.then<void>(data => console.log(`p3 branch1 ${data}`)); | |
p3.then<string>(data => new MyPromise<string>((res, rej) => res(data + 88))) | |
.then<void>(data => console.log(`p3 branch2 ${data}`)); | |
// p3 branch1 hahaha | |
// p3 branch2 hahaha88 | |
const p4 = MyPromise.all([ | |
new MyPromise<number>((resolve, reject) => resolve(4)), | |
new MyPromise<number>((resolve, reject) => setTimeout(() => resolve(5), 600)), | |
new MyPromise<number>((resolve, reject) => resolve(6)), | |
]) | |
.then<void>(data => console.log(`p4 resolved: ${data.reduce((rr, e) => rr + e)}`)); | |
// p4 resolved: 15 | |
const p5 = new MyPromise<string>((resolve, reject) => setTimeout(() => reject('too bad'), 700)) | |
.then<string>(data => data, err => 'backup') | |
.then<void>(data => console.log(`p4 idontknow: ${data}`)); | |
// p4 idontknow: backup |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment