Skip to content

Instantly share code, notes, and snippets.

@jhunterkohler
Created July 31, 2021 15:25
Show Gist options
  • Save jhunterkohler/b919fde641e83552499c2018e4d2c8ed to your computer and use it in GitHub Desktop.
Save jhunterkohler/b919fde641e83552499c2018e4d2c8ed to your computer and use it in GitHub Desktop.
A partially implemented A+ compliant Promise class
// 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