preface
Promise has become a must-have skill for front-end development. It mainly solves the callback hell problem caused by a large number of nested callback functions in asynchronous programming. In this article, I will study it with you in the form of learning notes. The content mainly includes the basic usage and handwriting two parts, suitable for beginners and want to in-depth understanding of the principle of small partners.
Know the Promise
CommonJS
The community came up with it firstPromise
The specification, theES2015
Is standardized and becomes a language specificationPromise
Is an object that indicates whether an asynchronous task succeeds or fails after completion. The initial state of any Promise object isPending
When the task succeeds, the status changes toFulfilled
, and then performs a callback for the successful taskonFufilled
; When the task fails, the status changes toRejected
, and then performs the callback for the failed taskonRejected
- States can only be created from
Pending
intoFulfilled
Or fromPending
becomeRejected
There are only two kinds of changes, and after the changes willCan’t beThis will be a big pity, which is very depressing. If I am invited tonight, this will be a pity. If I am not invited tonight, this will be my Rejected. It can’t be changed.
Promise basic usage
Step 1: Create an instance
Promise is a new global object in ES2015. Create a Promise instance with new and pass in an Executor function that starts executing immediately. The main business process is in the Executor function
const promise = new Promise((resolve,reject) = >{
if(/* Asynchronous operation succeeded */){
resolve('Executed successfully')}else{
reject(new Error('Execution failed'))}})Copy the code
The resolve and reject functions are passed as arguments to the executor function
- call
resolve
Delta function, and put ourPromise
Change the status toFulfilled
And pass the result of the successful asynchronous operation as a parameter - call
reject
Delta function, and put ourPromise
Change the status toRejected
And pass the reason why the asynchronous operation failed as a parameter - You can only call one or the other, either success or failure
- It’s important to note that,
Promise
Is used to manage asynchrony, but it’s not asynchrony itself,executor
The function will execute immediately, and we tend to be inexecutor
Write asynchronous operations in
Step 2: Call the then method
After the Promise instance is created, use its then method to specify the onFulfilled or onRejected callback function. The first parameter is onFulfilled and the second parameter is onFulfilled. Where onRejected can be omitted.
promise.then( value= >{
console.log('resolved',value)
}, error= >{ // the onRejected callback can be omitted
console.log('rejected',error)
})
Copy the code
Execution sequence
Even if we don’t perform any asynchronous operations, the callback in promise.then is executed after the JS has completed its synchronous task:
const promise = new Promise((resolve,reject) = >{
console.log(1)
resolve(100)
console.log(2)
})
promise.then( value= >{
console.log('resolved',value)
}, error= >{
console.log('rejected',error)
})
console.log('3')
/* Output result */
/ / 1
/ / 2
/ / 3
// resolved 100
Copy the code
Execute the executor function first, printing 1 and 2. Resolve is a microtask, and proceed with the synchronization task. Store both the success and failure callbacks (stored in the variables of the Promie instance, as described in the handwritten Promise), do not execute them, and then print 3. At this point, the synchronization task is complete, the microtask is executed, and the success callback of the then method is called. Print Out Resolved 100
Then let’s add the timer:
const promise = new Promise((resolve,reject) = >{
console.log(1)
resolve(100)
console.log(2)})// Add timer here, what is the order of execution?
setTimeout(() = >{
console.log('setTimeout')},0)
promise.then( value= >{
console.log('resolved',value)
}, error= >{
console.log('rejected',error)
})
console.log('3')
/* Output result */
/ / 1
/ / 2
/ / 3
// resolved 100
// setTimeout
Copy the code
If setTimeout is 0, the system immediately enters the callback queue and waits for the next round of execution. There is a myth here, because setTimeout executes before Promise, we would think that setTimeout would be queued first, so it would execute first, but that’s not the case.
Because Promise belongs to the microtask, setTimeout belongs to the macro task. After the current synchronization code is executed, if there is a microtask, the microtask will be executed, if there is a macro task, it will wait until the next queue to execute. For the concept and difference between macro task and microtask, please refer to microtask, macro task and Event-loop
Microtasks are designed to improve overall responsiveness. In JS, most asynchronous tasks are macro tasks, and only Promise, MutationObserver and Process. nextTick in Node are executed as microtasks.
Common Mistakes
The essence of Promise is still callback. When the asynchronous task ends, callback is executed through then method. Some students will nest callback unnaturally, because they are not clear about the use of Promise and do not know the characteristics of chain invocation of Promise.
promiseA.then(function(value1){
promiseB.then(function(value2){
promiseC.then(function(value3){
/* Callback hell */. })})})Copy the code
The chain call to Promise
The then method returns a new Promise object after execution, so you can use chained calls to avoid callback hell and make your code flat.
The chain invocation of a Promise differs from the traditional chain invocation:
- Traditional chain calls return in a function
this
Promise
The chain call is inthen
Returns a new one inPromise
object
const promise1 = new Promise((resolve,reject) = >{
resolve(100)})const promise2 = promise1.then((value) = >{
console.log(value)
})
const promsie3 = promise2.then((value) = >{
console.log(value)
})
console.log(promise1 === promise2) // false proves that a new Promise object is returned
console.log(promise2 === promise3) // false
Copy the code
Since then returns a new Promise, the current call to THEN adds a state-changed callback to the Promise returned by the previous THEN:
const promise = new Promise((resolve,reject) = >{
resolve(100)})/* chain call */
promise
.then((value) = >{
console.log('11111')
})
.then((value) = >{
console.log('22222')
})
.then((value) = >{
console.log('33333')})Copy the code
We can either manually return a new Promise in the THEN method, or we can return a plain value:
promise
.then((value) = >{
// Manually return a new Promise
return new Promise((resolve,reject) = >{
resolve(800)
})
})
.then((value) = >{
console.log(value) / / 800
// Manually return the normal value
return 'hello promise'
})
.then((value) = >{
console.log(value) // hello promise
})
.then((value) = >{
console.log(value) // undefined returns no value for the last then
})
Copy the code
Exception handling
promise
.then(function(value){
console.log(1)
})
.then(function(value){
throw Error('error')
})
.then(function(value){
console.log(2)
})
.catch(function(error){
console.log('onRejected',error)
})
/ / output
/ / 1
// error
Copy the code
- Used at the end of a chain call
catch
Catch the exception and specify the failed callback - It catches all the exceptions in the whole chain call, sort of “bubbling”, passing them backwards until they are called
catch
capture - This way, we don’t have to be in every
then
In the writingonRejected
Callbacks don’t have to be everythen
Write backcatch
, which makes our code more elegant
A static method
Promise.resolve( )
If you receive a normal value, return a Promise with the normal value as a result
// Return the Fulfilled Promise object
Promise.resolve('hello world')
.then(value= >{
console.log(value) // hello world
})
/ / equivalent to the
new Promise((resolve,reject) = >{
resolve('hello world')})Copy the code
If another Promise object is received, it is returned as is
const promise = new Promise((resolve,reject) = >{})
const promise2 = Promise.resolve(promise)
console.log(promise === promise2) // true
Copy the code
Promise.reject( )
Return a failed Promise that will fail no matter what parameter is passed
cosnt promise = Promise.reject('hello world')
Copy the code
Promise.all( )
Combine multiple Promise instances into a new Promise instance that returns a different value for success than for failure, with success returning an array containing the execution results of each Promise, and failure returning the original REJECT value.
Promise.all
When allPromise
A successful callback is executed if all objects succeed, and a failed callback is executed if any object fails
const promise = Promise.all([
Promise.resolve({code:200.data: [1.2]}),
Promise.resolve({code:200.data: [3.4]}),
Promise.resolve({code:200.data: [5.6]}),
])
promise.then(function(values){
console.log(values) // Values are an array containing the results of each promise asynchronous task
})
Copy the code
For example, if we want to request multiple interfaces at the same time and perform the next step when all data is returned, we can use the promise.all method
Note that the array of successful results obtained by promise. all is the same order as the instance passed in when calling Promise.all. In the above code, interface 1 is requested, interface 2 is requested, interface 3 is requested, even if interface 1 is the last to get the result, it is first.
Promise.allSettled( )
Sometimes we don’t care about the outcome of asynchronous operations, just whether they are finished, so we introduced the Promise.allSettled method in ES2020
Promise.allSettled([
Promise.resolve({code:200.data: [1.2]}),
Promise.reject({code:500.errMsg:'Server exception'}),
Promise.resolve({code:200.data: [3.4]})
])
.then(function(values){
console.log(values)
})
Copy the code
Promise.allsettled is very similar to promise.all, except that Promise.AllSettled will get the state of every Promise, whether it succeeds or fails.
Promie.race( )
Multiple Promise calls are also supported, but unlike promise.all, race stands for a race, and promise.race ([P1,p2,p3]) returns whichever result executes fastest, whether it succeeds or fails
Promise.all
The newly returned Promise will not be completed until all tasks have been completedPromise.race
As long as one task succeeds, the newly returned Promise succeeds, and as long as one Promise fails, the returned Promise fails
Promise.finally( )
In ES9, the finally method will execute the callback function specified by the finally function, no matter the result is pity or Rejected. This avoids the situation where we write operations in then and catch respectively
// Make the page go around while requesting the interface
this.loading = true
axios.get('http://juejin.cn/api')
.then(res= > {
// this.loading = false
})
.catch(err= > {
// this.loading = false
})
.finally(() = > {
this.loading = false
})
Copy the code
Write a Promise
We couldn’t just lay out the full Promise code because it wouldn’t make sense. We had to start from scratch and work our way through it. So I write comments at key points in each step and learn by comparing the code before and after. It seems like a lot of code, but the emphasis is on incremental code.
A basic Promise
To write promises by hand, summarize how they’ll be used and their features:
Promise
throughnew
Come out, it must be a constructor or a class, and we’ll implement it as a class- Receives an executor function
executor
And can be executed immediately - The executor function receives
resolve
和reject
They are used for changePromise
The state of the Promise
There are three states:Pendding
,Fulfilled
,Rejected
And once it is determined, it cannot be changed- through
then
Method determines the status and executes if it succeedsonFulfilled
If the command fails, the command is executedonRejected
// Define three state constants
const PENDDING = 'PENDDING';
const FULFILLED = 'FULFILLED';
const REJECTED = 'REJECTED';
class MyPromise {
// Receive an executor function
constructor(executor) {
Execute executor immediately, passing resolve and reject
executor(this.resolve, this.reject)
}
// Each Promise instance has its own state, starting with PENDDING
status = PENDDING
// Cache success results and failure causes
value = undefined
reason = undefined
resolve = value= > {
/** * 1. After the asynchronous function of the business code succeeds, call resolve and pass in the success value value * 2. We call resolve directly from executor. If this were a normal function, this would point to the window. We need this to point to the Promise instance * 4. If the state is not PENDDING, changes are not allowed */
if (this.status ! == PENDDING)return
this.status = FULFILLED
// Save the successful value for future use in successful callbacks to the THEN method
this.value = value
}
reject = reason= > {
// Similar to resolve, reject is used to handle asynchronous task failures
if (this.status ! == PENDDING)return
this.status = REJECTED
// Save the cause of the failure for future use in the failure callback of the then method
this.reason = reason
}
/** * 1. Each Promise instance has then methods, so define THEN in the class * 2. The THEN method takes two arguments, a success callback and a failure callback * 3. Determine which callback to call based on the state and pass in the corresponding value */
then(onFulfilled, onRejected) {
if (this.status === FULFILLED) {
onFulfilled(this.value)
} else if (this.status === REJECTED) {
onRejected(this.reason)
}
}
}
// Don't forget to export
module.exports = MyPromise
Copy the code
Test if it’s available
const MyPromise = require('./promise.js')
let promise = new MyPromise((resolve, reject) = > {
resolve('success')
})
promise.then(value= > {
console.log(value)
}, reason= > {
console.log(reason)
})
Copy the code
Handling asynchronous cases
Next, in the asynchronous case of the business, we add a timer to the business code
const MyPromise = require('./promise.js')
let promise = new MyPromise((resolve, reject) = > {
// Async
setTimeout(() = >{
resolve('success');
},2000)})// The main thread does not wait for setTimeout, but executes here first
promise.then(value= > {
console.log(value)
}, reason= > {
console.log(reason)
})
Copy the code
The main thread does not wait for setTimeout to execute down to then, but resolve or reject has not yet been executed, which means that the Promise state is still PENDDING, so add a judgment to then: If the state is PENDDING, store the onFulfilled and onRejected callback functions first, and then invoke the callback function when the resolve or Reject callback is performed after the asynchronous task is completed
const PENDDING = 'PENDDING';
const FULFILLED = 'FULFILLED';
const REJECTED = 'REJECTED';
class MyPromise {
constructor(executor) {
executor(this.resolve, this.reject)
}
status = PENDDING
value = undefined
reason = undefined
// Cache successful callback and failed callback
onFulfilled = undefined
onRejected = undefined
resolve = value= > {
if (this.status ! == PENDDING)return
this.status = FULFILLED
this.value = value
/** * For asynchrony, check whether the successful callback exists and call */ if so
this.onFulfilled && this.onFulfilled(this.value)
}
reject = reason= > {
if (this.status ! == PENDDING)return
this.status = REJECTED
this.reason = reason
/** * For asynchrony, check whether the failed callback exists and call */ if it does
this.onRejected && this.onRejected(this.reason)
}
then(onFulfilled, onRejected) {
if (this.status === FULFILLED) {
onFulfilled(this.value)
} else if (this.status === REJECTED) {
onRejected(this.reason)
} else {
/** * If it is PENDDING, it is asynchronous ** But we do not know whether it will succeed or fail in the future, so store them for now ** /
this.onFulfilled = onFulfilled
this.onRejected = onRejected
}
}
}
module.exports = MyPromise
Copy the code
Handle multiple callback cases
The then method is called multiple times, passing in either a successful or a failed callback that is to be executed, so modify it by storing these functions in an array and calling resolve and reject in turn
const PENDDING = 'PENDDING';
const FULFILLED = 'FULFILLED';
const REJECTED = 'REJECTED';
class MyPromise {
constructor(executor) {
executor(this.resolve, this.reject)
}
status = PENDDING
value = undefined
reason = undefined
// Use an array to store these callbacks instead
// onFulfilled = undefined
// onRejected = undefined
onFulfilled = []
onRejected = []
resolve = value= > {
if (this.status ! == PENDDING)return
this.status = FULFILLED
this.value = value
// Call all callbacks instead of just once
// this.onFulfilled && this.onFulfilled(this.value)
while (this.onFulfilled.length) {
this.onFulfilled.shift()(this.value)
}
}
reject = reason= > {
if (this.status ! == PENDDING)return
this.status = REJECTED
this.reason = reason
// Call all callbacks instead of just once
// this.onRejected && this.onRejected(this.reason)
while (this.onRejected.length) {
this.onRejected.shift()(this.reason)
}
}
then(onFulfilled, onRejected) {
if (this.status === FULFILLED) {
onFulfilled(this.value)
} else if (this.status === REJECTED) {
onRejected(this.reason)
} else {
// There can be multiple callbacks, all stored
// this.onFulfilled = onFulfilled
// this.onRejected = onRejected
this.onFulfilled.push(onFulfilled)
this.onRejected.push(onRejected)
}
}
}
module.exports = MyPromise
Copy the code
Chain calls to the then method
then
Methods can be called chained, and each time they returnPromise
object- On a
then
Is passed to the currentthen
The callback function of
const PENDDING = 'PENDDING';
const FULFILLED = 'FULFILLED';
const REJECTED = 'REJECTED';
class MyPromise {
constructor(executor) {
executor(this.resolve, this.reject)
}
status = PENDDING
value = undefined
reason = undefined
onFulfilled = []
onRejected = []
resolve = value= > {
if (this.status ! == PENDDING)return
this.status = FULFILLED
this.value = value
while (this.onFulfilled.length) {
this.onFulfilled.shift()(this.value)
}
}
reject = reason= > {
if (this.status ! == PENDDING)return
this.status = REJECTED
this.reason = reason
while (this.onRejected.length) {
this.onRejected.shift()(this.reason)
}
}
then(onFulfilled, onRejected) {
The /** * then method needs to return a new Promise instance that implements the chain call */
const newPromise = new MyPromise((resolve, reject) = > {
if (this.status === FULFILLED) {
// Get the success callback value of the current then
let current = onFulfilled(this.value)
/** * current may be a promise or a normal value, which is different for different cases. * if it is a normal value, call resolve. Call resolve or reject * we encapsulate the process into a function, which can be used in the same way as onRejected * we also need to compare newPromise and current to prevent them from being the same promise. That is, the problem with circular calls * */
resolvePromise(newPromise, current, resolve, reject)
} else if (this.status === REJECTED) {
let current = onRejected(this.reason)
resolvePromise(current, resolve, reject)
} else {
this.onFulfilled.push(onFulfilled)
this.onRejected.push(onRejected)
}
})
return newPromise
}
}
function resolvePromise(newPromise, current, resolve, reject) {
if (current === newPromise) {
return reject(new TypeError('Cannot loop'))}if (current instanceof MyPromise) {
current.then(value= > {
resolve(value)
}, reason= > {
reject(reason)
})
} else {
resolve(current)
}
}
module.exports = MyPromise
Copy the code
If you are careful, you will notice that newPromise is returned after the execution of newPromise is completed. The newPromise passed in when we call resolvePromise is not available yet, so we use setTimeout to convert it into asynchronous code
then(onFulfilled, onRejected) {
const newPromise = new MyPromise((resolve, reject) = > {
if (this.status === FULFILLED) {
// Switch to asynchronous code to get newPromise
setTimeout(() = > {
let current = onFulfilled(this.value)
resolvePromise(newPromise, current, resolve, reject)
}, 0);
} else if (this.status === REJECTED) {
setTimeout(() = > {
let current = onRejected(this.reason)
resolvePromise(current, resolve, reject)
}, 0);
} else {
this.onFulfilled.push(onFulfilled)
this.onRejected.push(onRejected)
}
})
return newPromise
}
Copy the code
Error handling mechanism
- If the executor function fails, it should be in
constructor
Is captured and thrown - if
onFulfilled
oronRejected
Errors should also be caught and thrown
const PENDDING = 'PENDDING';
const FULFILLED = 'FULFILLED';
const REJECTED = 'REJECTED';
class MyPromise {
constructor(executor) {
// Catch an error in the executor function
try {
executor(this.resolve, this.reject)
} catch (err) {
this.reject(err)
}
}
status = PENDDING
value = undefined
reason = undefined
onFulfilled = []
onRejected = []
resolve = value= > {
if (this.status ! == PENDDING)return
this.status = FULFILLED
this.value = value
while (this.onFulfilled.length) {
// No need to pass the value
// this.onFulfilled.shift()(this.value)
this.onFulfilled.shift()()
}
}
reject = reason= > {
if (this.status ! == PENDDING)return
this.status = REJECTED
this.reason = reason
while (this.onRejected.length) {
// No need to pass the value
// this.onRejected.shift()(this.reason)
this.onRejected.shift()()
}
}
then(onFulfilled, onRejected) {
const newPromise = new MyPromise((resolve, reject) = > {
if (this.status === FULFILLED) {
setTimeout(() = > {
// Catch ondepressing's mistake
try {
let current = onFulfilled(this.value)
resolvePromise(newPromise, current, resolve, reject)
} catch (err) {
reject(err)
}
}, 0);
} else if (this.status === REJECTED) {
setTimeout(() = > {
// Catch an error from onRejected
try {
let current = onRejected(this.reason)
resolvePromise(current, resolve, reject)
} catch (err) {
reject(err)
}
}, 0);
} else {
// Catch the error onFulfilled and onRejected
// this.onFulfilled.push(onFulfilled)
// this.onRejected.push(onRejected)
this.onFulfilled.push(() = > {
setTimeout(() = > {
// Catch ondepressing's mistake
try {
let current = onFulfilled(this.value)
resolvePromise(newPromise, current, resolve, reject)
} catch (err) {
reject(err)
}
}, 0);
})
this.onRejected.push(() = > {
setTimeout(() = > {
// Catch an error from onRejected
try {
let current = onRejected(this.reason)
resolvePromise(current, resolve, reject)
} catch (err) {
reject(err)
}
}, 0); })}})return newPromise
}
}
function resolvePromise(newPromise, current, resolve, reject) {
if (current === newPromise) {
return reject(new TypeError('Cannot loop'))}if (current instanceof MyPromise) {
current.then(value= > {
resolve(value)
}, reason= > {
reject(reason)
})
} else {
resolve(current)
}
}
module.exports = MyPromise
Copy the code
Realize the Promise. All ()
Promise.all
It’s a static methodstatic
The statement- Receives an array, one for each entry
Promise
Instance or ordinary value, iterates through the array when allPromise
After the execution, the result is returned
// promise. all is a static method declared static
static all(array) {
let result = [];
/ / counter
let index = 0;
return new MyPromise((resolve, reject) = > {
function addData(key, value) {
result[key] = value;
index++;
/** * If the for loop is complete and the asynchronous function has not returned a result, resolve will fail * so we wait for the loop to complete before we execute resolve ** /
if(index === array.length) { resolve(result); }}for (let i = 0; i < array.length; i++) {
let data = array[i];
if (data instanceof MyPromise) {
// Data is the case for the Promise object
data.then(value= > addData(i, value), reason= > reject(reason))
} else {
// data is a normal value
addData(i, data)
}
}
})
}
Copy the code
Implement promise.resolve () and promise.reject ()
These two are relatively simple, direct masturbation!
static resolve(value) {
return new MyPromise((resolve, reject) = > {
resolve(value)
})
}
static reject(value) {
return new MyPromise((resolve, reject) = >{ reject(err); })}Copy the code
If finally’s callback returns an asynchronous Promise object
To realize the catch
If we call the THEN method without passing a failure callback, the error will eventually be caught by a catch
catch (onRejected) {
return this.then(undefined, onRejected)
}
Copy the code
Complete code
const PENDDING = 'PENDDING';
const FULFILLED = 'FULFILLED';
const REJECTED = 'REJECTED';
class MyPromise {
constructor(executor) {
try {
executor(this.resolve, this.reject)
} catch (err) {
this.reject(err)
}
}
status = PENDDING
value = undefined
reason = undefined
onFulfilled = []
onRejected = []
resolve = value= > {
if (this.status ! == PENDDING)return
this.status = FULFILLED
this.value = value
while (this.onFulfilled.length) {
this.onFulfilled.shift()()
}
}
reject = reason= > {
if (this.status ! == PENDDING)return
this.status = REJECTED
this.reason = reason
while (this.onRejected.length) {
this.onRejected.shift()()
}
}
then(onFulfilled, onRejected) {
const newPromise = new MyPromise((resolve, reject) = > {
if (this.status === FULFILLED) {
setTimeout(() = > {
try {
let current = onFulfilled(this.value)
resolvePromise(newPromise, current, resolve, reject)
} catch (err) {
reject(err)
}
}, 0);
} else if (this.status === REJECTED) {
setTimeout(() = > {
try {
let current = onRejected(this.reason)
resolvePromise(current, resolve, reject)
} catch (err) {
reject(err)
}
}, 0);
} else {
this.onFulfilled.push(() = > {
setTimeout(() = > {
try {
let current = onFulfilled(this.value)
resolvePromise(newPromise, current, resolve, reject)
} catch (err) {
reject(err)
}
}, 0);
})
this.onRejected.push(() = > {
setTimeout(() = > {
try {
let current = onRejected(this.reason)
resolvePromise(current, resolve, reject)
} catch (err) {
reject(err)
}
}, 0); })}})return newPromise
}
static all(array) {
let result = [];
let index = 0;
return new MyPromise((resolve, reject) = > {
function addData(key, value) {
result[key] = value;
index++;
if(index === array.length) { resolve(result); }}for (let i = 0; i < array.length; i++) {
let data = array[i];
if (data instanceof MyPromise) {
data.then(value= > addData(i, value), reason= > reject(reason))
} else {
addData(i, data)
}
}
})
}
static resolve(value) {
return new MyPromise((resolve, reject) = > {
resolve(value)
})
}
static reject(value) {
return new MyPromise((resolve, reject) = >{ reject(err); })}catch (onRejected) {
return this.then(undefined, onRejected)
}
}
function resolvePromise(newPromise, current, resolve, reject) {
if (current === newPromise) {
return reject(new TypeError('Cannot loop'))}if (current instanceof MyPromise) {
current.then(value= > {
resolve(value)
}, reason= > {
reject(reason)
})
} else {
resolve(current)
}
}
module.exports = MyPromise
Copy the code
Write in the last
Write here basically almost, although there are points that can be optimized in the code, but through the arrangement of this article, we have deepened the impression of Promise again, learning is a process of forgetting, we need to continue to consolidate and deepen memory, come on, friends!
The resources
- Do you really know Promise
- Promise from entry to the handwritten | [Promise a series]