Last active
March 26, 2019 06:05
-
-
Save softwarespot/97f2baa7041295b915e8fe8c471cda08 to your computer and use it in GitHub Desktop.
Non-spec compliant 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
// NOTE: The following is a non-spec compliant Promise implementation | |
// Utils | |
const asyncRun = createAsyncRunner() | |
const isFunction = fn => typeof fn === 'function' | |
const isObject = obj => Object(obj) === obj | |
const noop = () => {} | |
const STATE_PENDING = 'pending' | |
const STATE_RESOLVED = 'resolved' | |
const STATE_REJECTED = 'rejected' | |
function Promise(executor) { | |
var isInstance = this instanceof Promise | |
if (!isInstance) { | |
throw new TypeError('"Promise" cannot be called as a function. Use the "new" keyword to construct a new "Promise" object') | |
} | |
if (!isFunction(executor)) { | |
throw new TypeError(`Invalid type for "executor" argument, got "${typeof executor}"`) | |
} | |
if (isInstance && (this.state === STATE_PENDING || this.state === STATE_RESOLVED)) { | |
throw new TypeError('Invalid "this", cannot be a pending or resolved "Promise" object') | |
} | |
this.state = STATE_PENDING | |
this.value = undefined | |
this.chains = [] | |
this.resolve = createSetStateFn(this, STATE_RESOLVED) | |
this.reject = createSetStateFn(this, STATE_REJECTED) | |
try { | |
executor(this.resolve, this.reject) | |
// const resolver = createResolutionFn(this) | |
// executor(resolver, this.reject) | |
} catch (ex) { | |
this.reject(ex) | |
} | |
} | |
Promise.prototype.then = function thenFn(onResolved, onRejected) { | |
const chain = new this.constructor(noop, noop) | |
chain.onResolved = isFunction(onResolved) ? onResolved : undefined | |
chain.onRejected = isFunction(onRejected) ? onRejected : undefined | |
this.chains.push(chain) | |
handleChains(this) | |
return chain | |
} | |
Promise.prototype.catch = function catchFn(onRejected) { | |
return this.then(undefined, onRejected) | |
} | |
Promise.resolve = function resolveFn(value) { | |
// Return as-is, if an instance of Promise | |
if (isObject(value) && value.constructor === this) { | |
return value | |
} | |
// NOTE: "this" is bound to Promise by default | |
return new this(resolve => resolve(value)) | |
} | |
Promise.reject = function rejectFn(reason) { | |
return new this((_, reject) => reject(reason)) | |
} | |
function handleChains(promise) { | |
if (promise.state === STATE_PENDING) { | |
return | |
} | |
promise.chains.forEach((chain) => { | |
// Skip those chains, which have already been handled before | |
if (chain.state !== STATE_PENDING) { | |
return | |
} | |
const onHandler = promise.state === STATE_RESOLVED ? chain.onResolved : chain.onRejected | |
if (onHandler) { | |
asyncRun(() => { | |
try { | |
const fn = createResolutionFn(chain) | |
fn(onHandler(promise.value)) | |
} catch (ex) { | |
chain.reject(ex) | |
} | |
}) | |
} else if (promise.state === STATE_RESOLVED) { | |
chain.resolve(promise.value) | |
} else if (promise.state === STATE_REJECTED) { | |
chain.reject(promise.value) | |
} | |
}) | |
} | |
function createResolutionFn(promise) { | |
return (value) => { | |
// Spec: 2.3.1 | |
if (promise === value) { | |
promise.reject(new TypeError(`A Promise's callback cannot return the same Promise instance`)) | |
return | |
} | |
// Spec: 2.3.2 | |
if (value instanceof Promise) { | |
// Spec: 2.3.2.1 | |
// Spec: 2.3.2.2 | |
// Spec: 2.3.2.3 | |
value.then(promise.resolve, promise.reject) | |
// const fn = createResolutionFn(promise) | |
// value.then(fn, promise.reject) | |
return | |
} | |
// Spec: 2.3.4 | |
if (!isFunction(value) && !isObject(value)) { | |
promise.resolve(value) | |
return | |
} | |
// Spec: 2.3.3 | |
let then | |
try { | |
// Spec: 2.3.3.1 | |
then = value.then | |
} catch (ex) { | |
// Spec: 2.3.3.2 | |
promise.reject(ex) | |
return | |
} | |
// Spec: 2.3.3.4 | |
if (!isFunction(then)) { | |
promise.resolve(value) | |
return | |
} | |
// Spec: 2.3.3.3.3 | |
try { | |
// Spec: 2.3.3.3 | |
// Spec: 2.3.3.3.1 | |
// Spec: 2.3.3.3.2 | |
// Spec: 2.3.3.3.3 | |
then.call(value, promise.resolve, promise.reject) | |
// const fn = createResolutionFn(promise) | |
// then.call(value, fn, promise.reject) | |
} catch (ex) { | |
// Spec: 2.3.3.3.4 | |
// Spec: 2.3.3.3.4.1 | |
// Spec: 2.3.3.3.4.2 | |
promise.reject(ex) | |
} | |
} | |
} | |
function createSetStateFn(promise, nextState) { | |
return (valueOrReason) => { | |
if (promise.state === STATE_PENDING) { | |
promise.state = nextState | |
promise.value = valueOrReason | |
handleChains(promise) | |
} | |
} | |
} | |
function createAsyncRunner() { | |
const fns = [] | |
// Flush the queue by calling all functions pushed to the queue. | |
function flush() { | |
// NOTE: Using arr.forEach() does not work due to issues of | |
// copying the array | |
for (let i = 0; i < fns.length; i += 1) { | |
const fn = fns[i] | |
fn() | |
} | |
fns.length = 0 | |
} | |
return (fn) => { | |
if (fns.push(fn) === 1) { | |
setTimeout(flush) | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment