Hand write a Promise source code
The step-by-step process of fulfilling a Promise. Start by implementing a simple promise
class MyPromise {
Execute the executor function immediately
constructor(executor) {
executor(this.resolve, this.reject)
}
onFulfilledCallbacks = [] // Define a successful callback array
onRejectedCallbacks = [] // Define an array of failed callbacks
status = 'PENDING' // Define A state, according to Promises/A+, once A state changes, it cannot be changed
value = null // The state becomes FULLFULED, which is regarded as the successful callback (value) of the. Then
reason = null // When the status changes to REJECTED, consider the.then callback to the onRejected function (reason).
// As the first argument to execute executor immediately, it is used to trigger a successful callback. Then
resolve = (value) = > {
if(this.status === 'PENDING') {
// If the state is' waiting ', change the state to 'succeeded'
this.status = 'FULFILLED'
// Provide arguments to the successful callback to.then
this.value = value
/ / if it is an asynchronous call resolve, enclosing onFulfilledCallbacks in. Then collect the incoming onFulfulled function method
this.onFulfilledCallbacks.forEach(fn= > fn(value))
}
}
reject = (reason) = > {
if(this.status === 'PENDING') {
// If the state is' waiting ', change the state to 'failed' first.
this.status = 'REJECTED'
// Arguments provided to.then failed callback (reason)
this.reason = reason
// If you call reject asynchronously, this.onRejectedCallbacks will collect the onRejected function in the. Then method
this.onRejectedCallbacks.forEach(fn= > fn(reason))
}
}
then = function(onFulfilled, onRejected) {
if(this.status === 'PENDING') {
New MyPromise(resolve, reject) calls resolve or reject asynchronously.
this.onFulfilledCallbacks.push((value) = > { onFulfilled(value) })
this.onRejectedCallbacks.push((reason) = > { onRejected(reason) })
}
if(this.status === 'FULFILLED') {
New MyPromise(resolve, reject) calls resolve synchronously.
onFulfilled(this.value)
}
if(this.status === 'REJECTED') {
Reject is called synchronously in new MyPromise(resolve, reject).
onRejected(this.reason)
}
}
}
Copy the code
This completes the simplest of Promose shells.
So what we’re going to do is implement chain calls to dot then. How to chain call? Then returns a new Promise instance, so let’s implement it from the original.
then(onFulfilled, onRejected) {
const promise2 = new MyPromise((resolve, reject) = > {
if(this.status === 'FULFILLED') {
onFulfilled(this.value)
} else if(this.status === 'REJECTED') {
onRejected(this.reason)
} else if(this.status === 'PENDING') {
this.onFulfilledCallbacks.push((value) = > { onFulfilled(value) })
this.onRejectedCallbacks.push((reason) = > { onRejected(reason) })
}
})
// Return the new Promise to subsequent.then calls
return promise2
}
Copy the code
If.then() then() then() then() then() then() then() then() then() then() then() then() then() then() then() then() then() then() then() then() then() then() then() then() then() then() then() then()) then() then() then() then() then()) then() then() then()) So that’s the Promise instance that was returned by the last dot then, so it’s pretty obvious what to do next. Modify the then function above.
then(onFulfilled, onRejected) {
const promise2 = new MyPromise((resolve, reject) = > {
if(this.status === 'FULFILLED') {
let value = onFulfilled(this.value)
resolve(value)
} else if(this.status === 'REJECTED') {
let reason = onRejected(this.reason)
reject(reason)
} else if(this.status === 'PENDING') {
this.onFulfilledCallbacks.push((value) = > { onFulfilled(value) })
this.onRejectedCallbacks.push((reason) = > { onRejected(reason) })
}
})
// Return the new Promise to subsequent.then calls
return promise2
}
Copy the code
Let’s test that out
new MyPromise(resolve= > {
resolve(1)
}).then(value= > {
console.log(value)
return value + 1
}).then(value= > {
console.log(value)}) output:1 2
Copy the code
Ok, Promise/A+ :.then() can pass no arguments, so how do we pass resolve or reject to the next.then? Look at the code below.
then(onFulfilled, onRejected) {
// This is a big pity. If onFulfilled is not a function, we will turn it into a function and return the value passed by the last promise instance
const realOnFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value= > value
// If onRejected is not a function, we will turn it into a function, which will be similar to realondepressing
const realOnRejected = typeof onRejected === 'function' ? onRejected : reason= > {
throw reason
}
const promise2 = new MyPromise((resolve, reject) = > {
if(this.status === 'FULFILLED') {
let value = realOnFulfilled(this.value)
resolve(value)
} else if(this.status === 'REJECTED') {
let reason = realOnRejected(this.reason)
reject(reason)
} else if(this.status === 'PENDING') {
this.onFulfilledCallbacks.push((value) = > { realOnFulfilled(value) })
this.onRejectedCallbacks.push((reason) = > { realOnRejected(reason) })
}
})
// Return the new Promise to subsequent.then calls
return promise2
}
Copy the code
Now test this feature
new MyPromise(resolve= > {
resolve(1)
}).then().then(value= > {
console.log(value)
})
Copy the code
Ok, if now. Then resolve is a Promise instance. The current version obviously doesn’t work. So how do we fix it?
then(onFulfilled, onRejected) {
// This is a big pity. If onFulfilled is not a function, we will turn it into a function and return the value passed by the last promise instance
const realOnFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value= > value
// If onRejected is not a function, we will turn it into a function, which will be similar to realondepressing
const realOnRejected = typeof onRejected === 'function' ? onRejected : reason= > {
throw reason
}
const promise2 = new MyPromise((resolve, reject) = > {
if(this.status === 'FULFILLED') {
// If the return value of x is a Promise instance
let x = realOnFulfilled(this.value)
// We define a resolvePromise function that does the same thing: call reslove, but with a different treatment for the return value
resolvePromise(x, resolve, reject)
} else if(this.status === 'REJECTED') {
// This is a pity if the return value of x here is a Promise instance
let x = realOnRejected(this.reason)
resolvePromise(x, resolve, reject)
} else if(this.status === 'PENDING') {
this.onFulfilledCallbacks.push((value) = > { realOnFulfilled(value) })
this.onRejectedCallbacks.push((reason) = > { realOnRejected(reason) })
}
})
// Return the new Promise to subsequent.then calls
return promise2
}
function resolvePromise(x, resolve, reject) {
if (typeof x === 'object' || typeof x === 'function') {
if(x === null) {
return resolve(x)
}
let then
try {
then = x.then
} catch(err) {
reject(err)
}
if (then === 'function') {
try {
then.call(
x,
y= > {
// Call resolvePromise recursively if a Promise instance is returned and resolve a Promise instance
resolvePromise(y, resolve, reject)
},
r= > {
reject(r)
}
)
} catch(err) {
}
} else {
// If it is an object value, just call resolve
resolve(x)
}
} else {
// If it is a normal value, just call resolve
resolve(x)
}
}
Copy the code
Test the
new MyPromise(resolve= > {
resolve(1)
}).then(value= > {
return new MyPromise(resolve= > {
resolve(value + 1)
})
}).then(value= > {
console.log(value)
})
Copy the code
Ok, resolved successfully. So let’s say we have code now
let p1 = new MyPromise(resolve= > {
resolve(1)
})
p1.then(value= > {
return p1
})
Copy the code
Uncaught (in promise) TypeError: Chaining cycle detected for promise #< promise >, at this time we should check whether the return value of ondepressing in then is equal to the current promise instance, modify the source code
then(onFulfilled, onRejected) {
// This is a big pity. If onFulfilled is not a function, we will turn it into a function and return the value passed by the last promise instance
const realOnFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value= > value
// If onRejected is not a function, we will turn it into a function, which will be similar to realondepressing
const realOnRejected = typeof onRejected === 'function' ? onRejected : reason= > {
throw reason
}
const promise2 = new MyPromise((resolve, reject) = > {
if(this.status === 'FULFILLED') {
// Add a queueMicrotask that reads promise2 to the next microtask, so that no error is reported when promise2 is not read
queueMicrotask(() = > {
// If the return value of x is a Promise instance
let x = realOnFulfilled(this.value)
// We define a resolvePromise function that does the same thing: call reslove, but with a different treatment for the return value
resolvePromise(promise2, x, resolve, reject)
})
} else if(this.status === 'REJECTED') {
// Add a queueMicrotask that reads promise2 to the next microtask, so that no error is reported when promise2 is not read
queueMicrotask(() = > {
try {
// This is a pity if the return value of x here is a Promise instance
const x = realOnRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error)
}
})
} else if(this.status === 'PENDING') {
this.onFulfilledCallbacks.push((value) = > { realOnFulfilled(value) })
this.onRejectedCallbacks.push((reason) = > { realOnRejected(reason) })
}
})
// Return the new Promise to subsequent.then calls
return promise2
}
function resolvePromise(promise, x, resolve, reject) {
// Same reject error
if (promise === x) {
return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))}if (typeof x === 'object' || typeof x === 'function') {
if(x === null) {
return resolve(x)
}
let then
try {
then = x.then
} catch(err) {
reject(err)
}
if (then === 'function') {
try {
then.call(
x,
y= > {
// Call resolvePromise recursively if a Promise instance is returned and resolve a Promise instance
resolvePromise(promise, y, resolve, reject)
},
r= > {
reject(r)
}
)
} catch(err) { reject(err); }}else {
// If it is an object value, just call resolve
resolve(x)
}
} else {
// If it is a normal value, just call resolve
resolve(x)
}
}
Copy the code
Ok, no problem, and finally post the full code
class MyPromise {
constructor(executor) {
try {
executor(this.resolve, this.reject)
} catch(err) {
throw err
}
}
onFulfilledCallbacks = []
onRejectedCallbacks = []
status = 'PENDING'
value = null
reason = null
resolve = (value) = > {
if(this.status === 'PENDING') {
this.status = 'FULFILLED'
this.value = value
this.onFulfilledCallbacks.forEach(fn= > fn(value))
}
}
reject = (reason) = > {
if(this.status === 'PENDING') {
this.status = 'REJECTED'
this.reason = reason
this.onRejectedCallbacks.forEach(fn= > fn(reason))
}
}
then(onFulfilled, onRejected) {
// This is a big pity. If onFulfilled is not a function, we will turn it into a function and return the value passed by the last promise instance
const realOnFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value= > value
// If onRejected is not a function, we will turn it into a function, which will be similar to realondepressing
const realOnRejected = typeof onRejected === 'function' ? onRejected : reason= > {
throw reason
}
const promise2 = new MyPromise((resolve, reject) = > {
const fulfilledMicrotask = () = > {
// Add a queueMicrotask that reads promise2 to the next microtask, so that no error is reported when promise2 is not read
queueMicrotask(() = > {
try{
// If the return value of x is a Promise instance
let x = realOnFulfilled(this.value)
// We define a resolvePromise function that does the same thing: call reslove, but with a different treatment for the return value
resolvePromise(promise2, x, resolve, reject)
} catch(error) {
reject(error)
}
})
}
const rejectedMicrotask = () = > {
// Add a queueMicrotask that reads promise2 to the next microtask, so that no error is reported when promise2 is not read
queueMicrotask(() = > {
try {
// This is a pity if the return value of x here is a Promise instance
const x = realOnRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error)
}
})
}
if(this.status === 'FULFILLED') {
fulfilledMicrotask()
} else if(this.status === 'REJECTED') {
rejectedMicrotask()
} else if(this.status === 'PENDING') {
this.onFulfilledCallbacks.push(fulfilledMicrotask)
this.onRejectedCallbacks.push(rejectedMicrotask)
}
})
// Return the new Promise to subsequent.then calls
return promise2
}
catch (onRejected) {
// Just error handling
this.then(undefined, onRejected); }}function resolvePromise(promise, x, resolve, reject) {
// Same reject error
if (promise === x) {
return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))}if (typeof x === 'object' || typeof x === 'function') {
if(x === null) {
return resolve(x)
}
let then
try {
then = x.then
} catch(err) {
reject(err)
}
if (typeof then === 'function') {
let called = false;
try {
then.call(
x,
y= > {
if (called) return;
called = true;
// Call resolvePromise recursively if a Promise instance is returned and resolve a Promise instance
resolvePromise(promise, y, resolve, reject)
},
r= > {
if (called) return;
called = true;
reject(r)
}
)
} catch(err) {
if (called) return; reject(err); }}else {
// If it is an object value, just call resolve
resolve(x)
}
} else {
// If it is a normal value, just call resolve
resolve(x)
}
}
MyPromise.deferred = function () {
var result = {};
result.promise = new MyPromise(function (resolve, reject) {
result.resolve = resolve;
result.reject = reject;
});
return result;
}
module.exports = MyPromise
Copy the code
To check the Promise/A+ specification, create A new package.json
// package.json
{
"name": "promise"."version": "1.0.0"."description": "my promise"."main": "promise.js"."scripts": {
"test": "promises-aplus-tests promise"
},
"author": "Wayag"."license": "ISC"."devDependencies": {
"promises-aplus-tests": "^ 2.1.2"}}Copy the code
Run NPM run test.
Let’s talk about the automatic execution of generator and the simple implementation of async await. Let’s look at the manual execution of generator.
function* myGenerator() {
console.log(yield Promise.resolve(1)) / / 1
console.log(yield Promise.resolve(2)) / / 2
console.log(yield Promise.resolve(3)) / / 3
}
const gen = myGenerator()
gen.next().value.then(val= > {
console.log(val) / / 1
gen.next(val).value.then(val= > {
console.log(val) / / 2
gen.next(val).value.then(val= > {
console.log(val) / / 3
gen.next(val)
})
})
})
Copy the code
The next step is to replace the manual with automatic, recursive and we can do that very quickly.
function run (generator) {
let g = generator()
function _next (val) {
let res = g.next(val)
if (res.done) return res.value
Promise.resolve(res.value).then(_next ,reject)
}
_next()
}
run(myGenerator)
Copy the code
According to async await we should return a Promise instance at the end and we need to modify the source code
function run (generator) {
return new Promise((resolve, reject) = > {
let g = generator()
function _next (val) {
let res = g.next(val)
if (res.done) return resolve(res.value)
Promise.resolve(res.value).then(_next ,reject)
}
_next()
})
}
Copy the code