Created
July 31, 2021 15:25
-
-
Save jhunterkohler/b919fde641e83552499c2018e4d2c8ed to your computer and use it in GitHub Desktop.
A partially implemented A+ compliant Promise class
This file contains hidden or 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
// const GlobalPromise = global.Promise; | |
// delete global.Promise; | |
function typename(value) { | |
if (typeof value == "object" && typeof value != "null") { | |
let name; | |
try { | |
name = value.constructor?.name; | |
} catch {} | |
return name ? `#<${name}>` : Object.prototype.toString.call(value); | |
} else { | |
return String(value); | |
} | |
} | |
class Promise { | |
constructor(executor) { | |
// this._value = null; | |
// this._reason = null; | |
// this._resolved = false; | |
// this._rejected = false; | |
// this._head = null; | |
// this._tail = null; | |
if (typeof executor != "function") { | |
throw new TypeError( | |
`Promise resolver ${typename(executor)} is not a function` | |
); | |
} | |
try { | |
executor(this._resolve.bind(this), this._reject.bind(this)); | |
} catch (reason) { | |
this._reject(reason); | |
} | |
} | |
// [[Resolve]] - Promise Resolution Procedure | |
_resolve(value) { | |
if (this._resolved) { | |
return; | |
} | |
if (this == value) { | |
this._reject(new TypeError("Cyclic promise chain detected")); | |
} else if (value instanceof Promise) { | |
if (value._resolved) { | |
if (value._rejected) { | |
this._reject(value._reason); | |
} else { | |
this._resolve(value._reason); | |
} | |
} else { | |
value.then(this._resolve.bind(this), this._reject.bind(this)); | |
} | |
} else if (typeof value?.then == "function") { | |
let then; | |
try { | |
then = value.then; | |
} catch (err) { | |
this._reject(err); | |
} | |
try { | |
then.call( | |
value, | |
this._resolve.bind(this), | |
this._reject.bind(this) | |
); | |
} catch (err) { | |
this._reject(err); | |
} | |
} else { | |
this._fulfill(value); | |
} | |
} | |
_fulfill(value) { | |
this._value = value; | |
this._resolved = true; | |
this._rejected = false; | |
while (this._head) { | |
if (typeof this._head.onResolved == "function") { | |
queueMicrotask(() => { | |
try { | |
this._head.resolve(this._head.onFulfilled(value)); | |
} catch (err) { | |
this._head.reject(err); | |
} | |
}); | |
} | |
this._head = this._head.next; | |
} | |
} | |
_reject(reason) { | |
this._reason = reason; | |
this._resolved = true; | |
this._rejected = true; | |
while (this._head) { | |
if (typeof this._head.onRejected == "function") { | |
queueMicrotask(() => { | |
try { | |
this._head.resolve(this._head.onRejected(reason)); | |
} catch (err) { | |
this._head.reject(err); | |
} | |
}); | |
} | |
this._head = this._head.next; | |
} | |
} | |
then(onFulfilled, onRejected) { | |
let child; | |
let promise = new Promise((resolve, reject) => { | |
child = { resolve, reject, onFulfilled, onRejected }; | |
}); | |
this._tail = this._tail | |
? (this._tail.next = child) | |
: (this._head = child); | |
return promise; | |
} | |
catch(onRejected) { | |
return this.then(undefined, onRejected); | |
} | |
finally(onFinally) { | |
return this.then( | |
(value) => (onFinally(), value), | |
(value) => { | |
onFinally(); | |
throw value; | |
} | |
); | |
} | |
static resolve(value) { | |
return new Promise((resolve) => resolve(value)); | |
} | |
static reject(reason) { | |
return new Promise((_, reject) => reject(reason)); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment