Working people! For the soul! It’s the front end! This series is summed up in the process of learning in the road of big front attack. If there is something wrong in the article, I hope you can criticize and correct it and make progress with each other. Please indicate the source of reprint.
Hand tear Promise source code
We introduced the Promise in the last big front-end attack road (two)JS asynchronous programming, we not only want to use it, but also to understand how it is realized, so that our cerebellum is awake, with my footsteps!
1. Core logic analysis and implementation of Promise
- The first thing we need to know is that a Promise is a class, and when we use that class, we need to pass in an executor, and that executor will execute immediately.
- There are three states in a Promise: pending; Successful fuilfilled; Failure is rejected. The initial state is pending, and the wait state can be converted to success or failure. This state, once established, cannot be changed.
- A promise contains resolve and reject functions that change the state of the promise. This is a pity. ‘resolve’ is a pity. ‘reject’ is a pity.
- The promise prototype object has a then method that determines the state, and if the state is successful, the success callback is called. If the state is failed, the failure callback function is called.
- The then method takes two functions as arguments, a success callback that executes on success and a failure callback that executes on failure, passed in by promise’s resolve and Reject, respectively.
// myPromise.js
// Constant is defined for reuse and code prompts
const PENDING = 'pending' / / wait for
const FULFILLED = 'fulfilled' / / success
const REJECTED = 'rejected' / / fail
// Define a constructor
class MyPromise {
constructor (executor) {
// Executor is an executor that executes immediately upon entry, passing in the resolve and reject methods
executor(this.resolve, this.reject)
}
// An attribute of the instance object, starting with wait
status = PENDING
// Value after success
value = undefined
// The cause of the failure
reason = undefined
// Resolve and reject
If called directly, the normal function this refers to window or undefined
// Use the arrow function to make this point to the current instance object
resolve = value= > {
// Check whether the state is waiting to prevent the program from running down
if(this.status ! == PENDING)return
// Change the status to successful
this.status = FULFILLED
// Save the value after success
this.value = value
}
reject = reason= > {
if(this.status ! == PENDING)return
// Change the status to failed
this.status = REJECTED
// The cause of the save failure
this.reason = reason
}
then (successCallback, failCallback) {
// Determine the status
if(this.status === FULFILLED) {
// Call the successful callback and return the value
successCallback(this.value)
} else if (this.status === REJECTED) {
// Call the failed callback and return the reason
failCallback(this.reason)
}
}
}
module.exports = MyPromise
Copy the code
So we’ve implemented a simple version of Promise, so let’s test it out!
//promise.js
const MyPromise = require('./myPromise')
let promise = new MyPromise((resolve, reject) = > {
resolve('success')
reject('err')
})
promise.then(value= > {
console.log('resolve', value)
}, reason= > {
console.log('reject', reason)
})
// Print resolve success
Copy the code
After the test, the above code can be executed normally, but we usually use Promise to handle requests asynchronously. We use setTimeout to simulate the asynchronous situation.
//promise.js
const MyPromise = require('./myPromise')
let promise = new MyPromise((resolve, reject) = > {
// We expect a delay of 2 seconds before a successful callback is executed and the parameters are printed
// But the result is nothing back to us
// This is because the main thread executes immediately, setTimeout is asynchronous, and then executes immediately
The state is pending, so all callbacks in the then state will not be executed
setTimeout(() = > {
resolve('success')},2000);
})
promise.then(value= > {
console.log('resolve', value)
}, reason= > {
console.log('reject', reason)
})
Copy the code
To be able to handle this asynchronous processing, we need to tweak our promises
// myPromise.js
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
class MyPromise {
constructor (exector) {
exector(this.resolve, this.reject)
}
status = PENDING
value = undefined
reason = undefined
// Define a successful callback parameter
successCallback = undefined
// Define a failure callback parameter
failCallback = undefined
resolve = value= > {
if(this.status ! == PENDING)return
this.status = FULFILLED
this.value = value
// Determine if the successful callback exists and call it if it does
this.successCallback && this.successCallback(this.value)
}
reject = reason= > {
if(this.status ! == PENDING)return
this.status = REJECTED
this.reason = reason
// Determine if the failed callback exists and invoke it if it does
this.failCallback && this.failCallback(this.reason)
}
then (successCallback, failCallback) {
if(this.status === FULFILLED) {
successCallback(this.value)
} else if (this.status === REJECTED) {
failCallback(this.reason)
} else {
/ / wait for
Success and failure callbacks are stored because the status is not known
// Wait until the function succeeds or fails
this.successCallback = successCallback
this.failCallback = failCallback
}
}
}
module.exports = MyPromise
Copy the code
The general idea is that we add pending status when executing THEN, store our success and failure callbacks, and when resolve or reject is executed, determine whether a success or failure callback exists, and execute if it does. Now let’s execute promise.js and see if we can print it out after 2s
Don’t feel good cowhide Promise to achieve a sense of accomplishment! But let’s look at the other case.
//promise.js
const MyPromise = require('./myPromise')
let promise = new MyPromise((resolve, reject) = > {
setTimeout(() = > {
resolve('success')},2000);
})
promise.then(value= > {
console.log(1)
console.log('resolve1', value)
})
promise.then(value= > {
console.log(2)
console.log('resolve2', value)
})
promise.then(value= > {
console.log(3)
console.log('resolve3', value)
})
Copy the code
The result of this code execution is to print 3 and resolve3 success after a delay of 2 seconds, which is clearly not what we expected.
- Then methods on the same Promise object can be called multiple times.
- For synchronous callbacks, simply execute them; For asynchronous callbacks, we need to store the successful and failed callbacks in an array and wait until the state of the Promise object changes.
We continue to work on the code!
// myPromise.js
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
class MyPromise {
constructor (exector) {
exector(this.resolve, this.reject)
}
status = PENDING
value = undefined
reason = undefined
// Define an empty array for a successful callback
successCallback = []
// Define an empty array of failed callbacks
failCallback = []
resolve = value= > {
if(this.status ! == PENDING)return
this.status = FULFILLED
this.value = value
// Determine if the successful callback exists and call it if it does
// Loop the callback array. Pop out the method in front of the array and call it directly
// The shift method returns the first item of the array, changing the array
// If the array is empty, all callbacks are executed
while(this.successCallback.length) this.successCallback.shift()(this.value)
}
reject = reason= > {
if(this.status ! == PENDING)return
this.status = REJECTED
this.reason = reason
// Determine if the failed callback exists and invoke it if it does
// Same as success
while(this.failCallback.length) this.failCallback.shift()(this.reason)
}
then (successCallback, failCallback) {
if(this.status === FULFILLED) {
successCallback(this.value)
} else if (this.status === REJECTED) {
failCallback(this.reason)
} else {
/ / wait for
// Store both successful and failed callbacks in an array
this.successCallback.push(successCallback)
this.failCallback.push(failCallback)
}
}
}
module.exports = MyPromise
Copy the code
After such a revamp, we go back to implement promise.js and find that the result is as we expected ~ of course, there is still a very important feature that is not implemented. What problem did we learn Promise to solve? Yeah, that callback hell problem that makes our brains ache and our eyes ache.
Chain calls to the then method solve the callback hell problem for us, so how do we implement chain calls?
- Then methods that want to chain calls need to return a Promise object
- The return value of the then method is used as an argument to the next THEN method
- If we return a Promise object, we need to determine the state of the promise
// myPromise.js
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
class MyPromise {
constructor (exector) {
exector(this.resolve, this.reject)
}
status = PENDING
value = undefined
reason = undefined
successCallback = []
failCallback = []
resolve = value= > {
if(this.status ! == PENDING)return
this.status = FULFILLED
this.value = value
while(this.successCallback.length) this.successCallback.shift()(this.value)
}
reject = reason= > {
if(this.status ! == PENDING)return
this.status = REJECTED
this.reason = reason
while(this.failCallback.length) this.failCallback.shift()(this.reason)
}
then (successCallback, failCallback) {
The then method returns the first Promise object
let promise2 = new Promise((resolve, reject) = > {
if(this.status === FULFILLED) {
// x is the return value of the previous Promise callback
// Determine whether x is a normal value or a promise object
// Call resolve directly if it is plain paper
// If it is a Promise object, see the result returned by the Promise object
// Call resolve or reject based on the result returned by the Promise object
let x = successCallback(this.value)
resolvePromise(x, resolve, reject)
} else if (this.status === REJECTED) {
let x = failCallback(this.reason)
resolvePromise(x, resolve, reject)
} else {
this.successCallback.push(() = > {
let x = successCallback(this.value)
resolvePromise(x, resolve, reject)
})
this.failCallback.push(() = > {
let x = failCallback(this.value)
resolvePromise(x, resolve, reject)
})
}
});
return promise2
}
}
function resolvePromise(x, resolve, reject) {
// Determine if x is an instance object
if(x instanceof MyPromise) {
/ / promise object
// x.then(value => resolve(value), reason => reject(reason))
// After simplification
x.then(resolve, reject)
} else{
/ / common values
resolve(x)
}
}
module.exports = MyPromise
Copy the code
Another thing we need to consider here is that if the then method returns its own promise object, the promise doll will happen, and we need to report an error when that happens.
// myPromise.js
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
class MyPromise {
constructor (exector) {
exector(this.resolve, this.reject)
}
status = PENDING
value = undefined
reason = undefined
successCallback = []
failCallback = []
resolve = value= > {
if(this.status ! == PENDING)return
this.status = FULFILLED
this.value = value
while(this.successCallback.length) this.successCallback.shift()(this.value)
}
reject = reason= > {
if(this.status ! == PENDING)return
this.status = REJECTED
this.reason = reason
while(this.failCallback.length) this.failCallback.shift()(this.reason)
}
then (successCallback, failCallback) {
The then method returns the first Promise object
let promise2 = new Promise((resolve, reject) = > {
if(this.status === FULFILLED) {
// Because the new Promise has to be executed before promise2 is implemented, there is no pormise2 in the synchronization code.
// This part of the code needs to be executed asynchronously
setTimeout(() = > {
let x = successCallback(this.value)
// Then the return promise is the same as the original promise.
// Determine if x is the same as promise2, so pass promise2 to resolvePromise
resolvePromise(promise2, x, resolve, reject)
}, 0);
} else if (this.status === REJECTED) {
// This part of the code needs to be executed asynchronously
setTimeout(() = > {
let x = failCallback(this.reason)
resolvePromise(x, resolve, reject)
}, 0);
} else {
this.successCallback.push(() = > {
// This part of the code needs to be executed asynchronously
setTimeout(() = > {
let x = successCallback(this.value)
resolvePromise(x, resolve, reject)
}, 0);
})
this.failCallback.push(() = > {
// This part of the code needs to be executed asynchronously
setTimeout(() = > {
let x = failCallback(this.value)
resolvePromise(x, resolve, reject)
}, 0); }}}));return promise2
}
}
function resolvePromise(promise2, x, resolve, reject) {
// If it is equal, return itself, throws a type error, and returns
if (promise2 === x) {
return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))}if(x instanceof MyPromise) {
x.then(resolve, reject)
} else{
resolve(x)
}
}
module.exports = MyPromise
Copy the code
So far we’ve implemented the chained call to THEN, but we haven’t done anything to catch and handle errors in our Promise class.
We use try catch to catch and handle errors. If our actuator or subsequent THEN methods have errors, we need to change the Promise state to the Rejected state.
// myPromise.js
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
class MyPromise {
constructor (exector) {
// Catch errors, reject if there are any
try {
exector(this.resolve, this.reject)
} catch (e) {
this.reject(e)
}
}
status = PENDING
value = undefined
reason = undefined
successCallback = []
failCallback = []
resolve = value= > {
if(this.status ! == PENDING)return
this.status = FULFILLED
this.value = value
// Async callback
// There is no need to pass the value when it is called, because it has already been handled by the push below
while(this.successCallback.length) this.successCallback.shift()()
}
reject = reason= > {
if(this.status ! == PENDING)return
this.status = REJECTED
this.reason = reason
// Async callback
// There is no need to pass the value when it is called, because it has already been handled by the push below
while(this.failCallback.length) this.failCallback.shift()()
}
then (successCallback, failCallback) {
let promise2 = new Promise((resolve, reject) = > {
if(this.status === FULFILLED) {
setTimeout(() = > {
Reject if an error is reported in the callback
try {
let x = successCallback(this.value)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
}, 0)}else if (this.status === REJECTED) {
setTimeout(() = > {
Reject if an error is reported in the callback
try {
let x = failCallback(this.reason)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
}, 0)}else {
// Handle asynchronous success error cases
this.successCallback.push(() = > {
setTimeout(() = > {
Reject if an error is reported in the callback
try {
let x = successCallback(this.value)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
}, 0)})this.failCallback.push(() = > {
setTimeout(() = > {
Reject if an error is reported in the callback
try {
let x = failCallback(this.reason)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
}, 0)})}});return promise2
}
}
function resolvePromise(promise2, x, resolve, reject) {
if (promise2 === x) {
return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))}if(x instanceof MyPromise) {
x.then(resolve, reject)
} else{
resolve(x)
}
}
module.exports = MyPromise
Copy the code
Let’s do a little test
// promise.js
const MyPromise = require('./myPromise')
let promise = new MyPromise((resolve, reject) = > {
// An asynchronous method
setTimeout(() = >{
resolve('succ')},2000)
})
promise.then(value= > {
console.log(1)
console.log('resolve', value)
return 'aaa'
}, reason= > {
console.log(2)
console.log(reason.message)
return 100
}).then(value= > {
console.log(3)
console.log(value);
}, reason= > {
console.log(4)
console.log(reason.message)
})
/ / 1
// resolve succ
/ / 3
// aaa
Copy the code
In practice, the parameters in the then method are optional, we can pass one or no parameters, the implementation is actually very simple.
// myPromise.js
then (successCallback, failCallback) {
If there is a callback, select the callback. If there is no callback, pass a function to pass the argument
successCallback = successCallback ? successCallback : value= > value
// Error functions also perform assignments, throwing error messages
failCallback = failCallback ? failCallback : reason= > {throw reason}
let promise2 = new Promise((resolve, reject) = >{... })... }// Simplify can also be written like this, using the default value of the parameter, if not, use the default value
then (successCallback = value= > value, failCallback = reason= > {throwA tiny} {...})Copy the code
So let’s test that out
// promise.js
const MyPromise = require('./myPromise')
let promise = new MyPromise((resolve, reject) = > {
resolve('succ')
})
promise.then().then().then(value= > console.log(value))
// succ
Copy the code
// promise.js
const MyPromise = require('./myPromise')
let promise = new MyPromise((resolve, reject) = > {
reject('err')
})
promise.then().then().then(value= > console.log(value), reason= > console.log(reason))
// err
Copy the code
Ok, so we just tore Promise up and stuffed it. Let’s take a look at some common ways to implement promises.
2.Promise.all method implementation
The promise.all method is used to solve asynchronous concurrency problems. Click on the portal for details
Let’s analyze it first:
- The all method receives an array of either normal values or promise objects
- The order of worth in the array must be the order in which we get the results
- The PROMISE return value is also a Promise object that can call the then method
- If all of the values in the array are successful, then the callback is successful, and if one of the values is failed, then the callback is failed
- If all is called directly from the class, then all must be a static method
//myPromise.js
static all (array) {
// Result array
let result = []
/ / counter
let index = 0
return new Promise((resolve, reject) = > {
let addData = (key, value) = > {
result[key] = value
index ++
// If the counter and array are the same length, then all elements are executed and the output is ready
if(index === array.length) {
resolve(result)
}
}
// Iterate over the passed array
for (let i = 0; i < array.lengt; i++) {
let current = array[i]
if (current instanceof MyPromise) {
The promise object executes then, adds the value to the array if resolve, and returns reject if it is an error
current.then(value= > addData(i, value), reason= > reject(reason))
} else {
// The normal values are added to the corresponding array
addData(i, array[i])
}
}
})
}
Copy the code
3.Promise. Race method implementation
The promise. all method returns only after all successes. The promise. race method returns only after one success or failure. Click on the portal for details
// Return as long as there is a success or failure
static race(array) {
let promise = new MyPromise((resolve, reject) = > {
for (let i = 0; i < array.length; i++) {
let curr = array[i];
// MyPromise instance result processing
if (curr instanceof MyPromise) {
curr.then(resolve, reject);
} else {
// Non-myPromise instance processingresolve(curr); }}});return promise;
}
Copy the code
4. Implementation of promise. resolve method
The resolve method returns a Promise object, if the argument is a Promise object, or if it is a normal value, generate a Promise object to return the value. Click on portal for details
// myPromise.js
static resolve (value) {
// If it is a Promise object, return it directly
if(value instanceof MyPromise) return value
// Return a promise object if it is a value, and return the value
return new MyPromise(resolve= > resolve(value))
}
Copy the code
5.Promise. Reject method implementation
Resolve is similar to the promise.resolve method implementation, but click on the portal for details
static reject(reason) {
// If it is an instance of MyPromise, it returns directly
if (reason instanceof MyPromise) return reason;
// If it is MyPromise instance otherwise return MyPromise instance
return new MyPromise((resolve, reject) = > reject(reason));
}
Copy the code
6. Promise. Prototype. Finally the realization of () method
- Finally executes whether the current final state succeeds or fails
- We can call then after finally to get the result
- This function is used on prototype objects
- Click on portal for details
// myPromise.js
finally (callback) {
// How do I get the current promise state, use the THEN method, and return callback anyway
// The then method returns a promise object, so we return the result of the then call
// We need to get a successful callback after the callback, so we need to return value too
// Failed callback also throws a cause
If callback were an asynchronous Promise object, we would wait for it to complete, so we would use the static resolve method
return this.then(value= > {
// Pass the promise returned after the callback call, execute the promise, and return value on success
return MyPromise.resolve(callback()).then(() = > value)
}, reason= > {
// Call the then method after a failure, and return the reason for the failure.
return MyPromise.resolve(callback()).then(() = > { throw reason })
})
}
Copy the code
7. Promise. Prototype. The realization of the catch () method
- The catch method is designed to catch all error callbacks to the Promise object
- Call the then method directly, passing undefined in successful places and Reason in wrong places
- A catch method is a method that acts on a prototype object
- Click on portal for details
// myPromise.js
catch (failCallback) {
return this.then(undefined, failCallback)
}
Copy the code
conclusion
Here we have implemented the core logic of Promise, and also implemented several common methods of Promise. In the process of implementation, some cases are simulated, such as using setTimeout to simulate microtasks. What is more important is that we should understand the internal realization principle and realization idea of Promise, so that we can have more ideas and solutions when we do the code in the future.
History article portal
- Big front – end approach (a) functional programming
- Big front-end attack road (two)JS asynchronous programming
reference
Pull hook big front end training camp