Skip to content

Instantly share code, notes, and snippets.

@Neo42
Last active January 18, 2022 10:43
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Neo42/5b1d2daeffe8e8ecb3521b61b3ad347d to your computer and use it in GitHub Desktop.
Save Neo42/5b1d2daeffe8e8ecb3521b61b3ad347d to your computer and use it in GitHub Desktop.
Simple promise implementation.
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