Last active
January 18, 2022 10:43
-
-
Save Neo42/5b1d2daeffe8e8ecb3521b61b3ad347d to your computer and use it in GitHub Desktop.
Simple 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
const states = { | |
PENDING: 'PENDING', | |
FULFILLED: 'FULFILLED', | |
REJECTED: 'REJECTED', | |
} | |
const isThenable = (thing) => thing && typeof thing.then === 'function' | |
class MyPromise { | |
_state = states.PENDING | |
_value = null | |
_reason = null | |
_thenCallbacks = [] | |
_finallyCallbacks = [] | |
constructor(executor) { | |
setTimeout(() => { | |
try { | |
executor?.(this._onFulfilled.bind(this), this._onRejected.bind(this)) | |
} catch (error) { | |
this._onRejected(error) | |
} | |
}) | |
} | |
then(fulfilledFn, rejectedFn) { | |
const controlledPromise = new MyPromise() | |
this._thenCallbacks.push([controlledPromise, fulfilledFn, rejectedFn]) | |
if (this._state === states.FULFILLED) { | |
this._propagateFulfilled() | |
} else if (this._state === states.REJECTED) { | |
this._propagateRejected() | |
} | |
return controlledPromise | |
} | |
catch(rejectedFn) { | |
return this.then(null, rejectedFn) | |
} | |
finally(sideEffectFn) { | |
if (this._state === states.PENDING) { | |
const controlledPromise = new MyPromise() | |
this._finallyCallbacks.push([controlledPromise, sideEffectFn]) | |
return controlledPromise | |
} | |
sideEffectFn() | |
return this._state === states.FULFILLED | |
? MyPromise.resolve(this._value) | |
: MyPromise.reject(this._reason) | |
} | |
_onFulfilled(value) { | |
if (this._state === states.PENDING) { | |
this._state = states.FULFILLED | |
this._value = value | |
this._propagateFulfilled() | |
} | |
} | |
_onRejected(reason) { | |
if (this._state === states.PENDING) { | |
this._state = states.REJECTED | |
this._reason = reason | |
this._propagateRejected() | |
} | |
} | |
_propagateFulfilled() { | |
this._thenCallbacks.forEach(([controlledPromise, fulfilledFn]) => { | |
if (typeof fulfilledFn === 'function') { | |
const maybeThenable = fulfilledFn(this._value) | |
if (isThenable(maybeThenable)) { | |
maybeThenable.then( | |
(value) => controlledPromise._onFulfilled(value), | |
(reason) => controlledPromise._onRejected(reason), | |
) | |
} else { | |
controlledPromise._onFulfilled(maybeThenable) | |
} | |
} else { | |
return controlledPromise._onFulfilled(this._value) | |
} | |
}) | |
this._finallyCallbacks.forEach(([controlledPromise, sideEffectFn]) => { | |
sideEffectFn() | |
controlledPromise._onFulfilled(this._value) | |
}) | |
this._finallyCallbacks = [] | |
this._thenCallbacks = [] | |
} | |
_propagateRejected() { | |
this._thenCallbacks.forEach(([controlledPromise, _, rejectedFn]) => { | |
if (typeof rejectedFn === 'function') { | |
const maybeThenable = rejectedFn(this._reason) | |
if (isThenable(maybeThenable)) { | |
maybeThenable.then( | |
(value) => controlledPromise._onFulfilled(value), | |
(reason) => controlledPromise._onRejected(reason), | |
) | |
} else { | |
controlledPromise._onFulfilled(maybeThenable) | |
} | |
} else { | |
return controlledPromise._onRejected(this._reason) | |
} | |
}) | |
this._finallyCallbacks.forEach(([controlledPromise, sideEffectFn]) => { | |
sideEffectFn() | |
controlledPromise._onRejected(this._reason) | |
}) | |
this._finallyCallbacks = [] | |
this._thenCallbacks = [] | |
} | |
} | |
MyPromise.resolve = (value) => new MyPromise((resolve) => resolve(value)) | |
MyPromise.reject = (value) => new MyPromise((_, reject) => reject(value)) | |
const promise = new MyPromise((resolve, reject) => { | |
setTimeout(() => { | |
reject(42) | |
}, 1000) | |
}).catch((error) => { | |
console.log(error) | |
return 'recovered' | |
}) | |
const firstThen = promise.then(console.log).finally(() => { | |
console.log('All done!') | |
}) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment