Question:
What is a Promise?
Quick answer:
It is an object which represents the current state and value of the operation. There are three states for Promises - pending, success, failure.
Longer answer:
Basically, the idea behind the Promises is fairly simple to understand. It is just a container that will only resolve when some calculations are done. That's it.
I guess it will be easier to understand if we just implement it on our own.
class MyPromise {
dataHandlers = []
errorHandler = []
finalHandlers = []
constructor(func) {
// Apply handlers one by one and initialize every following handler with the previouses result
let onResolve = data => this.dataHandlers.reduce(
(acc, onData) => onData(acc),
data
)
// Just call every onReject
let onReject = error => this.errorHandler.reduce(
(_, onError) => onError(error),
undefined
)
// Just call every onFinal
let onFinal = () => this.finalHandlers.reduce(
(_, onFinal) => onFinal(),
undefined
)
// We need to set timeout, so our function
// executed after we set .then, .catch, and .finally
setTimeout(() => {
try {
func(onResolve, onReject)
} catch (error) {
onReject(error)
} finally {
onFinal()
}
}, 0)
}
then(onData, onError) {
if (onData) { this.dataHandlers.push(onData) }
if (onError) { this.errorHandler.push(onError) }
return this
}
catch(onError) {
return this.then(undefined, onError)
}
finally(onFinal) {
if (onFinal) { this.finalHandlers.push(onFinal) }
return this
}
}
Let's test it!
let dataPromise = new MyPromise((resolve, reject) => resolve(2))
dataPromise
.then(res => res + 2)
.then(res => res * 2)
.then(res => console.log(res)) // 8
.finally(() => console.log('Finally!')) // Finally!
.finally(() => console.log('Finally (1)!')) // Finally (1)!
let rejectPromise = new MyPromise((resolve, reject) => reject(2))
rejectPromise
.then(res => res + 2)
.then(res => res * 2)
.then(res => console.log(res))
.catch(error => console.error(error)) // 2
.finally(() => console.log('Finally!')) // Finally!
let throwErrorPromise = new MyPromise((resolve, reject) => { throw new Error('hello') })
throwErrorPromise
.then(res => res + 2)
.then(res => res * 2)
.then(res => console.log(res))
.catch(error => console.error(error)) // hello
.finally(() => console.log('Finally!')) // Finally
// This one will produce two errors instead of one.
// Can you come up with the fix?
let doubleErrorPromise = new MyPromise((resolve, reject) => reject('first'))
doubleErrorPromise
.catch(error => { console.error(error); throw 'second' })
// 'first'
// 'second'
// Uncaught 'second'
// This is how it should work
let fixedDoubleErrorPromise = new Promise((resolve, reject) => reject('first'))
fixedDoubleErrorPromise
.catch(error => { console.error(error); throw 'second' })
// 'first'
// Uncaught 'second'
Real-life applications:
Sometimes it is a bit easier to use async/await syntax
And sometimes you will need Promise helpers functions like Promise.all
Resources:
MDN/Promise
Other posts:
- JS interview in 2 minutes / this π€―
- JS interview in 2 minutes / Encapsulation (OOP)
- JS interview in 2 minutes / Polymorphism (OOP)
Btw, I will post more fun stuff here and on Twitter. Let's be friends π
Top comments (5)
I have a question.
How do onResolve , onReject and onFinal methods at the constructor wait for all 'then' or 'catch' methods to be called ? You set the timeout to 0 and invoke 'func', 0 means run the code immediately.
Hey π Great question!
For example, if you use
setTimeout
, your function will be put into the execution queue, but not executed right away.will produce output
even though
console.log(3)
appears beforeconsole.log(1)
andconsole.log(2)
.MDN/Even toop
not precisely it means "run code in 0 seconds or more". This is because when running code with settimeout this is handled by timer API. Timer API waits given time (here 0 seconds) and then puts function (code that was contained within settimeout) in the message queue. Elements from message queue are run only if event stack is empty (this is far more complex than that and to get it I'm recommending this talk, guy does great job at explaining the event loop and js execution). This is why settimeout is sometimes used as an hack to run our code after call stack is empty (running currently functions ended the execution)
hey Nikita,
Thanks for sharing such a great article,
one more thing I suggest to replace setTimeout with queueMicrotask, since then/catch/finally callbacks is a microTasks and not Tasks as setTimeout callback.
in such a case promises callback will be passed by JS engine into the microTask queue.
Thanks for sharing
::)