** Let’s start with the humblest promise
let p=new Promise(function(resolev,reject){
console.log('execution')})Copy the code
The browser will type “Execute”…. From this we can infer that the promise structure should be:
Define a promise, passing a parameter that is a function that contains two methods, resolve and reject, that are immediately executed within the promise
function Promise(executor){
function resolve(){ }
function reject(){} // This executor can be thought of as a self-executing function.function(resolev,reject){
console.log('execution')
})(resolve,reject)
*/
executor(resolve,reject)
}
Copy the code
Promise has three states: pending, Resolved and Rejected. Promise cannot change its state until it is resolved
Pending ——————> Resolved ——————> successful…
Pending ——————>rejected waiting ——————> failed… ರ _ ರ
/ / the original promiselet p=new Promise(function(resolev,reject){// resolve(100) rejecte(200)}function(data){
console.log(data,'resolved')},function(error){
console.log(error,'rejecte')})Copy the code
In native promises, only the first one of resolve and Reject is called. And a call to resolve executes the first callback in THEN, and a call to Reject executes the second callback in THEN
To do this, we need to do it in our own promise function
- Define status, which stores the current status
- Define value to store the value passed in the successful call to resolve
- Define reason to store error messages for reject failed calls
- Define a THEN function and take two arguments of type function, the first a successful callback and the second a failed callback
- As you can see from the then method in a promise, only one of these methods will be executed
function Promise(executor) {
let self = this
this.status = 'pending'This. value = undefined // Successful value this.reason = undefined // Failed reasonfunction resolve(value) {
if (self.status == 'pending') {
self.status = 'resolved'
self.value = value
}
}
function reject(error) {
if (self.status == 'pending') {
self.status = 'rejected'
self.reason = error
}
}
executor(resolve, reject)
}
Promise.prototype.then = function// Only one method will be implemented according to the statusif (this.status == 'resolved') {
infulfilled(this.value)
}
if (this.status == 'rejected') {
inrejected(this.reason)
}
}
Copy the code
Now that promise is starting to look like something… It’s far from perfect, though, because the promise’s highlight is the problem of asynchronous callback hell, but the handwritten promise only supports synchronous code, as shown in the following example:
// Use your own promise~ not native ~~~!let p = new Promise(function (resolve, reject) {
setTimeout(() => {resolve(100)},1000)})function (data) {
console.log(data, 'resolve')},function (error) {
console.log(error, 'reject')})Copy the code
As you can see, the then method is not executed. Why
Actually it has been implemented.. Just because the state is still pending… Neither of the two if conditions is satisfied. So I’m going to skip it,
Now, why is that
- When we
new Promise(function (resolve, reject) { setTimeout(() => { resolve(100) },1000) })
whenexecutor()
It will be executed immediately, but don’t forget there’s asetTimeout
, soexecutor()
The action inside takes 1000ms - However, because the synchronous function is faster than the asynchronous function, so in this case
then
The method starts executing, howeverPromise
The inside of thestatus
Is stillpending
Wait state,then
Method scanned the interior… Found that status does not satisfy any of the conditions,so.. it’s ending… The end of the - After the 1000 ms,
executor()
In theresolve
It’s going to execute. It’s going tostatus
Changed toresolved
And assign the value tovalue
. But there was no follow-up,so.. it’s ending…
Make your promise support asynchronous calls
- Define two arrays onResovedCallbacks and onRejectedCallbacks in promise to hold successful and failed callbacks
- Judge in then
status
If it ispending
Wait state, then store successful and failed callbacks in the above two arrays respectively - Execute the onResovedCallbacks and onRjectedCallbacks in the Promise’s resolve and Reject methods
function Promise(executor) {
let self = this
this.status = 'pending'This. value = undefined // Store successful value this.reason = undefined // Store failed reason this.onResolvedCallbacks = []// Store successful callback This.onrejectedcallbacks = []// Store failed callbacksfunction resolve(value) {
if (self.status == 'pending') {
self.status = 'resolved'Self. Value = the value / / traverse, execute the infulfilled () method of the self. OnResolvedCallbacks. ForEach (fn = > fn ()); }}function reject(error) {
if (self.status == 'pending') {
self.status = 'rejected'Self. reason = errorinThe Rejected () method of the self. OnRejectedCallbacks. ForEach (fn = > fn ())}} executor (resolve, reject)} Promise. Prototype. Then =function (infulfilled, inrejected) {
let self = this
if (this.status == 'resolved') {
infulfilled(this.value)
}
if (this.status == 'rejected') {
inrejected(this.reason)
}
if (this.status == 'pending') {/ / at this moment is not resolved is not rejected state this. OnResolvedCallbacks. Push (function () {
infulfilled(self.value)
})
this.onRejectedCallbacks.push(function () {
inrejected(self.reason)
})
}
}
let p = new Promise(function (resolve, reject) {
setTimeout(() => {
resolve(100)
}, 2000)
})
p.then(function (data) {
console.log(data, 'resolve')},function (error) {
console.log(error, 'reject')})Copy the code
Now you can try setTimeout. Well, asynchronous calls are already supported
But there is another case, which is whennew Promise()
Throws an exception, for example
let p = new Promise(function (resolve, reject) {
throw new Error('wrong')
})
p.then(function (data) {
console.log(data, 'resolve')},function (error) {
console.log(error, 'reject')})Copy the code
In this case, the successful and failed callbacks to the THEN method will not be called. Instead, an error will be reported, because status is still pending, so we need to catch the exception inside the Promise function:
function Promise(executor) {
let self = this
this.status = 'pending'This. value = undefined // Store successful value this.reason = undefined // Store failed reason this.onResolvedCallbacks = []// Store successful callback This.onrejectedcallbacks = []// Store failed callbacksfunction resolve(value) {
if (self.status == 'pending') {
self.status = 'resolved'self.value = value self.onResolvedCallbacks.forEach(fn => fn()); }}function reject(error) {
if (self.status == 'pending') {
self.status = 'rejected'The self. "reason = error self. OnRejectedCallbacks. ForEach (fn = > fn ())}} / / catch exceptions to direct failed... try { executor(resolve, reject) } catch (error) { reject(error) } } Promise.prototype.then =function (infulfilled, inrejected) {
let self = this
if (this.status == 'resolved') {
infulfilled(this.value)
}
if (this.status == 'rejected') {
inrejected(this.reason)
}
if (this.status == 'pending') {
this.onResolvedCallbacks.push(function () {
infulfilled(self.value)
})
this.onRejectedCallbacks.push(function () {
inrejected(self.reason)
})
}
}
let p = new Promise(function (resolve, reject) {
throw new Error('wrong')
})
p.then(function (data) {
console.log(data, 'resolve')},function (error) {
console.log(error, 'reject111')})Copy the code
You can now successfully catch the exception
Now the handwritten promise from the original promise and more like a little bit, but the biggest feature of the original ~ chain call, we have not implemented,
Analyze the implementation method of ~ chain call
To ensure that a chained call returns a new one regardless of the success/failure/wait statePromise
To continue calling the THEN method
So promise2 equals newPromise
To resolve, the executor will be executed immediately when a new Promise is made
function Promise(executor) {
let self = this
this.status = 'pending'This. value = undefined // Store successful value this.reason = undefined // Store failed reason this.onResolvedCallbacks = []// Store successful callback This.onrejectedcallbacks = []// Store failed callbacksfunction resolve(value) {
if (self.status == 'pending') {
self.status = 'resolved'self.value = value self.onResolvedCallbacks.forEach(fn => fn()); }}function reject(error) {
if (self.status == 'pending') {
self.status = 'rejected'
self.reason = error
self.onRejectedCallbacks.forEach(fn => fn())
}
}
try {
executor(resolve, reject)
} catch (error) {
reject(error)
}
}
Promise.prototype.then = function (infulfilled, inrejected) {
let self = this
let promise2
if (this.status == 'resolved') {
promise2 = new Promise(function (resolve, reject) {
infulfilled(self.value)
})
}
if (this.status == 'rejected') {
promise2 = new Promise(function (resolve, reject) {
inrejected(self.reason)
})
}
if (this.status == 'pending') {
promise2 = new Promise(function (resolve, reject) {
self.onResolvedCallbacks.push(function () {
infulfilled(self.value)
})
self.onRejectedCallbacks.push(function () {
inrejected(self.reason)
})
})
}
return promise2
}
Copy the code
This code looks simple enough to return a new promise
Secondly, the native promise states that a successful callback or a failed callback will enter the successful callback of the next THEN if the result is returned, and the failed callback of the next THEN if there is an exception.
Let’s take a look at what then does after it throws an exception
*** Take a look at the following code
// Both native promises and today's handwritten promises can do thislet p = new Promise(function (resolve, reject) {
resolve(1000)
//reject(2000)
})
let p2 = p.then(function (data) {
throw new Error('wrong')},function (error) {
throw new Error('Failed')
})
p2.then(function (data) {
console.log(data, 'resolve');
}, function (error) {
console.log(error, 'rejected');
})
Copy the code
As you can see, if resolve() or reject() is used in the Promise instance, an exception is thrown in then and the failed callback for the next THEN is entered
Step by step, break down the execution flow of the catch exception, starting with the previous figure
Think about the ideas in the picture
- The first step
// Resolve was called first, so the promise status is Resolvedlet p = new Promise(function (resolve, reject) {
resolve(1000)
})
Copy the code
- Step 2 Call
then
Method,then
Method based on the current stateresolved
Enter the firstif
In the condition - 2.1 A new Promise instance is initialized
- 2.2 New Promiese instances will be executed immediately
infulfilled
This is called, but now indepressing throws an exception and is renewedpromise
try catch
Catch, reject method is called, newpromise
statestatus
Be changed toreject
Failed state,reason
Is equal to becatch
Error message received
let p2 = p.then(function (data) {
throw new Error('wrong')},function (error) {
throw new Error('Failed')})Copy the code
- Step three, now P2 is the new promise, and the state is
reject
reason
Is the error message that was just caught and enters the second if condition when it calls the then method because the current state isreject
Well, - 3.1 Newly initialize a Promise instance
- 3.2.. The Promise is executed immediately, the inRejected () method is called, then the second callback is executed. Print error message
p2.then(
function (data) {
console.log(data, 'resolve');
},
function (error) {
console.log(error, 'rejected');
})
Copy the code
Finally finished analyzing exception handling…
Now let’s analyze the return value of success or failure,
Native Primse executes the then method and may sometimes return a result, either a regular value or a promise, as the value of the next promise or as the reason for the failure
let p = new Promise(function (resolve, reject) {
resolve(1000)
})
let p2 = p.then(function(data) {// returns a normal value //return 'test'// Return a promisereturn new Promise(function (resolve, reject) {
//resolve('success')
reject('failure')})},function (error) {
throw new Error('failed')
})
p2.then(
function (data) {
console.log(data, 'resolveq');
},
function (error) {
console.log(error, 'rejected ');
})
Copy the code
To achieve this effect, we need to refine the then method of handwritten promises by writing a function resolvePromise that handles the result returned after the then method is executed
Promise.prototype.then = function (infulfilled, inrejected) {
let self = this
let promise2
if (this.status == 'resolved') {
promise2 = new Promise(function(resolve, reject) {//x can be a promise, or it can be a normal valueletThis is a big pity (self.value) // The resolve and reject values are promise2, and x is a big pity. The return value of X may be a promise or a normal value, so it will be processed uniformly resolvePromise(promise2, x, resolve, reject) }) }if (this.status == 'rejected') {
promise2 = new Promise(function(resolve, reject) {//x can be a promise, or it can be a normal valueletX = inrejected(self.reason) // The return value of X is promise2, which may be a promise or a common value, so it will be processed uniformly resolvePromise(promise2, x, resolve, reject) }) }if (this.status == 'pending') {
promise2 = new Promise(function (resolve, reject) {
self.onResolvedCallbacks.push(function() {//x can be a promise, or it can be a normal valueletThis is a big pity (self.value) // The resolve and reject values are promise2, and x is a big pity. The return value of X may be a promise or a normal value, so it will be processed uniformly resolvePromise(promise2, x, resolve, reject) }) self.onRejectedCallbacks.push(function() {//x can be a promise, or it can be a normal valueletX = inrejected(self.reason) // The return value of X is promise2, which may be a promise or a common value, so it will be processed uniformly resolvePromise(promise2, x, resolve, reject) }) }) }return promise2
}
Copy the code
Internal implementation of resolvePromise
- 1. First, in the native Promise, the result returned by the then method may be itself, which never succeeds or fails (circular reference) and throws an exception of the wrong type
So inside the resolvePromise you need to determine whether the result returned is the same as the Promise
- 2. Determine if x is the same
promise
If it ispromise
If x is an object and the then method of x is a function, then it is a promise - 3 if x is not
promise
That should be normal value straightresolve
- 4. If x is a
promise
Let’s see if he has onethen
methods
function resolvePromise(p2, x, resolve, reject) {
if(p2 === x && x ! = undefined) { reject(new TypeError('Type error'} // It could be a promise, see if there is one in the objectthenMethod, if there is a ~ then it's a promiseif(x ! == null && (typeof x ==='object' || typeof x === 'function') {try {// To prevent {then:11} In this case, judgment is requiredthenIs it a functionlet then = x.then
if (typeof then= = ='function') {
then.call(x, functionResolvePromise (p2, y, resolve, reject) resolvePromise(p2, y, resolve, reject)}function (err) {
reject(err)
})
} else{/ / ifthennotfunctionThat could be an object or a constant resolve(x)}} Catch (e) {reject(e)}}elseResolve (x)}}Copy the code
Perfect it… Should our code be able to pass nothing in THEN, implementation worth penetrating
let p = new Promise(function (resolve, reject) {
resolve(1000)
})
p.then().then().then(function (data) {
console.log(data, 'resolve');
}, function (error) {
console.log(error, 'reject');
})
Copy the code
Therefore, in the THEN method, there is a fault tolerance process for the parameter “Infulfilled” and “Rejected”. For the parameter “infulfilled”, the original parameter will be used. If there is no parameter, the default function will be given
Promise.prototype.then = function (infulfilled, inrejected) {
let self = this
letPromise2 // If indepressing is notfunctionThis is a default function that returns val indepressing = typeof indepressing ==='function' ? infulfilled : function (val) {
returnInt rejected = typeof inRejected ==='function' ? inrejected : function (err) {
throw err
}
if (this.status == 'resolved') {
promise2 = new Promise(function(resolve, reject) {//x can be a promise, or it can be a normal valuelet x = infulfilled(self.value)
resolvePromise(promise2, x, resolve, reject)
})
}
if (this.status == 'rejected') {
promise2 = new Promise(function(resolve, reject) {//x can be a promise, or it can be a normal valuelet x = inrejected(self.reason)
resolvePromise(promise2, x, resolve, reject)
})
}
if (this.status == 'pending') {
promise2 = new Promise(function (resolve, reject) {
self.onResolvedCallbacks.push(function() {//x can be a promise, or it can be a normal valuelet x = infulfilled(self.value)
resolvePromise(promise2, x, resolve, reject)
})
self.onRejectedCallbacks.push(function() {//x can be a promise, or it can be a normal valuelet x = inrejected(self.reason)
resolvePromise(promise2, x, resolve, reject)
})
})
}
return promise2
}
Copy the code
Finally, the ~promise specification requires that all the Infulfilled and the Rejected should be performed asynchronously, so here is the whole code for all the Infulfilled and the Rejected with setTimeout.
function Promise(executor) {
let self = this
this.status = 'pending'This. value = undefined // Store successful value this.reason = undefined // Store failed reason this.onResolvedCallbacks = []// Store successful callback This.onrejectedcallbacks = []// Store failed callbacksfunction resolve(value) {
if (self.status == 'pending') {
self.status = 'resolved'self.value = value self.onResolvedCallbacks.forEach(fn => fn()); }}function reject(error) {
if (self.status == 'pending') {
self.status = 'rejected'
self.reason = error
self.onRejectedCallbacks.forEach(fn => fn())
}
}
try {
executor(resolve, reject)
} catch (error) {
reject(error)
}
}
Promise.prototype.then = function (infulfilled, inrejected) {
let self = this
let promise2
infulfilled = typeof infulfilled === 'function' ? infulfilled : function (val) {
return val
}
inrejected = typeof inrejected === 'function' ? inrejected : function (err) {
throw err
}
if (this.status == 'resolved') {
promise2 = new Promise(function(resolve, reject) {//x can be a promise, or it can be a normal valuesetTimeout(function () {
try {
letx = infulfilled(self.value) resolvePromise(promise2, x, resolve, reject) } catch (err) { reject(err) } }); })}if (this.status == 'rejected') {
promise2 = new Promise(function(resolve, reject) {//x can be a promise, or it can be a normal valuesetTimeout(function () {
try {
letx = inrejected(self.reason) resolvePromise(promise2, x, resolve, reject) } catch (err) { reject(err) } }); })}if (this.status == 'pending') {
promise2 = new Promise(function (resolve, reject) {
self.onResolvedCallbacks.push(function() {//x can be a promise, or it can be a normal valuesetTimeout(function () {
try {
let x = infulfilled(self.value)
resolvePromise(promise2, x, resolve, reject)
} catch (err) {
reject(err)
}
});
})
self.onRejectedCallbacks.push(function() {//x can be a promise, or it can be a normal valuesetTimeout(function () {
try {
letx = inrejected(self.reason) resolvePromise(promise2, x, resolve, reject) } catch (err) { reject(err) } }); })})}return promise2
}
function resolvePromise(p2, x, resolve, reject) {
if(p2 === x && x ! = undefined) { reject(new TypeError('Type error'} // It could be a promise, see if there is one in the objectthenMethod, if there is a ~ then it's a promiseif(x ! == null && (typeof x ==='object' || typeof x === 'function') {try {// To prevent {then:11} In this case, judgment is requiredthenIs it a functionlet then = x.then
if (typeof then= = ='function') {
then.call(x, functionResolvePromise (p2, y, resolve, reject) resolvePromise(p2, y, resolve, reject)}function (err) {
reject(err)
})
} else{/ / ifthennotfunctionThat could be an object or a constant resolve(x)}} Catch (e) {reject(e)}}elseResolve (x)}}Copy the code
Ok ~promise finished. Suggestions are welcome.