Skip to content

Instantly share code, notes, and snippets.

@cleoold
Last active September 3, 2020 06:16
Show Gist options
  • Save cleoold/7e5e3ba2305e43d8d6b039fcfaa18ad0 to your computer and use it in GitHub Desktop.
Save cleoold/7e5e3ba2305e43d8d6b039fcfaa18ad0 to your computer and use it in GitHub Desktop.
basic javascript promise implementation
// 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}`);
});
// print
// 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}`));
// print
// 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)}`));
// print
// 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}`));
// print
// p4 idontknow: backup
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment