Let's build Promise from scratch.
My current understanding
The use of promises is basically to add a function to javascript's task queue once a value has been obtained by a long-running process (that process is usually carried out in a different thread or other part of the os, like when we request some value over the network or from hard disk).
Now p is container for the value we actually want. Later in our code, when we want to do something with that value we can do that like so:
what we've done above is basically we've added the function (given to .then
as an argument) to javascript's task queue. The event loop will pick it up when it gets time and add it to the call stack to be executed.
Breaking it down
So a promise is container for a value. It's constructor takes in a function. That function gets two arguments - a resolve
function and a reject
function. A promise also has a state indicating whether it is pending
, fulfilled
(resolve has been called), or rejected
(reject has been called).
Each time we call .then
on the promise we are adding a function to a list. When the promise is fulfilled
we need to execute all the functions in that list.
We'll implement the constructor first:
class MyPromise {
constructor(func) {
this.value = undefined
this.state = "pending"
this.fulfillmentTasks = []
this.rejectionTasks = []
this.resolve = this.resolve.bind(this)
this.reject= this.reject.bind(this)
if (func) {
func(this.resolve,this.reject)
}
}
resolve(value) {
if (this.state !== "pending") return this
this.state = "fulfilled"
this.value = value
this.rejectionTasks = []
this.fulfillmentTasks.map(t => setTimeout(t, 0))
this.fullfillmentTasks = []
return this
}
reject(value) {
if (this.state !== "pending") return this
this.state = "rejected"
this.value = value
this.fullfillmentTasks = []
this.rejectionTasks.map(t => setTimeout(t, 0))
this.rejectionTasks = []
return this
}
}
Implementing .then
MyPromise.prototype.then = function(onFulfilled, onRejected) {
switch (this.state) {
case "pending": {
this.fulfillmentTasks.push(() => onFulfilled(this.value))
this.rejectionTasks.push(() => onRejected(this.value))
break
}
case "fulfilled":
setTimeout(() => onFulfilled(this.value), 0)
break
case "rejected":
setTimeout(() => onRejected(this.value), 0)
break
}
}
const p = new MyPromise((res,rej) => {
res(2)
})
p.then((val) => console.log(val))
p.then((val) => console.log(val*2))
p.then((val) => console.log(val*3))
p.then((val) => console.log(val*4))
Chaining .then
callbacks:
For this we need to return a promise from .then
function
MyPromise.prototype.then = function(onFulfilled, onRejected) {
const newPromise = new MyPromise()
const fullfillmentTask = () => {
const newValue = onFulfilled(this.value)
newPromise.resolve(newValue)
}
switch (this.state) {
case "pending": {
this.fulfillmentTasks.push(fullfillmentTask)
this.rejectionTasks.push(() => onRejected(this.value))
break
}
case "fulfilled":
setTimeout(fullfillmentTask, 0)
break
case "rejected":
setTimeout(() => onRejected(this.value), 0)
break
}
return newPromise
}
const newp = new MyPromise((res,rej) => {
res(2)
})
newp.then((val) => val*2).then((val) => val*2).then((val) => val*2).then((val) => console.log("val ->", val))
module.exports.MyPromise = MyPromise
Flattening the .then
To handle the above scenario:
- p needs to "become" p1
- p's
value
is p1's value - p's state is p1's state
- we can't call
p.resolve()
once p1resolves
const imp = require("./main.js")
const MyPromise = imp.MyPromise
function isThenable(val) {
return typeof val === "object" && val !== null && typeof val.then === "function"
}
MyPromise.prototype.fulfill = function(val) {
if (isThenable(val)) return
this.value = val
this.state = "fulfilled"
this.rejectionTasks = []
this.fulfillmentTasks.map(t => setTimeout(t, 0))
this.fullfillmentTasks = []
}
MyPromise.prototype.resolve = function(value) {
if (this.settled) return this
this.settled = true
if (isThenable(value)) {
value.then((v) => this.fulfill(v))
} else {
this.fulfill(value)
}
return this
}
//testing the flattening pattern.
const p0 = new MyPromise((res) => {
const p1 = new MyPromise((r) => r(2))
res(p1)
})
p0.then(val => console.log("flattened val -> ", val))