The knowledge of promise can be divided into bronze, silver, gold, platinum, diamond, Xingyao and king. Maybe the king has played too much. Which stage have you been to?
About promise
bronze
You have used promise to solve asynchronous programming and know how to call then() and catch() to handle the callback function.
Basic Usage
const promise = new Promise(function(resolve, reject) { // ... some code if (/* Asynchronous operation succeeded */){ resolve(value); } else { // Asynchronous operation failed reject(error); } }); promise.then((value) => { // value passed by resolve // Do some callback processing after success }).catch((error) => { // Error is passed from reject(error); // Do some callback processing after asynchronous operation fails })
silver
You should know two characteristics and three disadvantages of promise.
Two characteristics
- The promise object has three states: pending, completed and rejected. The states of the three objects are not disturbed by the outside world
- Once the state changes, it will not change again. This result can be obtained at any time. There are only two possibilities for the state of Promise object to change: from pending to fully and from pending to rejected. As long as these two situations occur, the state will solidify and will not change again. This result will be maintained all the time. At this time, it is called resolved.
Three shortcomings
- Promise cannot be cancelled. Once it is created, it will be executed immediately. It cannot be cancelled halfway.
- Secondly, if the callback function is not set, the errors thrown by Promise will not be reflected to the outside.
- When it is in the pending state, it is impossible to know which stage it has reached (just started or about to be completed).
Promise.prototype.finally()
The finally() method is used to specify the operation that will be performed regardless of the final state of the Promise object. This method is the standard introduced by ES2018.
It's important to learn to use finally().
In the daily use of the front end, when we request the data of an interface through promise, when the request starts, we will put the page in the loading state, but after the interface request ends (whether successful or failed), we should end the loading state of the page.
// Make the page in loading state before calling the interface commit('setLoading', true); let checkResult = checkWarranty({SerialNumber: data.SN, CountryCode:config.user.Country}) .then((result: any) => { return result.data; }).finally(() => { // Cancel the loading status of the page after the interface is called commit('setLoading', false); });
I corrected many bug s because I didn't put the processing after the interface call in the finally callback, but processed it in then, without considering the failure of the interface request.
gold
promise.all
Promise. The all () method is used to wrap multiple promise instances into a new promise instance.
const p1 = new Promise((resolve, reject) => { if (/* Asynchronous operation succeeded */){ resolve('value1'); } else { reject('error1'); } }); const p2 = new Promise((resolve, reject) => { if (/* Asynchronous operation succeeded */){ resolve('value2'); } else { reject('error2'); } }); const p3 = new Promise((resolve, reject) => { if (/* Asynchronous operation succeeded */){ resolve('value3'); } else { reject('error3'); } }); const p = Promise.all([p1, p2, p3]);
In addition, Promise The parameter of the all () method can not be an array, but it must have an Iterator interface, and each member returned is a Promise instance.
The state of p is determined by p1, p2 and p3, which can be divided into two cases.
- Only when the states of p1, p2 and p3 become fully, the state of p will become fully. At this time, the return values of p1, p2 and p3 form an array and are passed to the callback function of p.
p.then((result) => { console.log(result) // [value1,value2,value3] })
- As long as one of p1, p2 and p3 is rejected, the state of p becomes rejected. At this time, the return value of the first rejected instance will be passed to the callback function of p.
p.catch((error) => { console.log(error) // error1 or error2 or error3 })
Note that if the Promise instance as a parameter defines its own catch method, once it is rejected, it will not trigger Promise Catch method of all().
const p1 = new Promise((resolve, reject) => { reject('error1'); }).catch((error) => { console.log('p1',error) }); const p2 = new Promise((resolve, reject) => { resolve('value2') }); Promise.all([p1,p2]).catch((error) => { console.log('promise.all', error ) }) // p1 error1
If p1 does not have its own catch method, it will execute promise Catch method of all
const p1 = new Promise((resolve, reject) => { reject('error1'); }) const p2 = new Promise((resolve, reject) => { resolve('value2') }); Promise.all([p1,p2]).catch((error) => { console.log('promise.all', error ) }) // promise.all error1
promise.race
Promise. The race () method also wraps multiple promise instances into a new promise instance.
const p1 = new Promise((resolve, reject) => { if (/* Asynchronous operation succeeded */){ resolve('value1'); } else { reject('error1'); } }); const p2 = new Promise((resolve, reject) => { if (/* Asynchronous operation succeeded */){ resolve('value2'); } else { reject('error2'); } }); const p3 = new Promise((resolve, reject) => { if (/* Asynchronous operation succeeded */){ resolve('value3'); } else { reject('error3'); } }); const p = Promise.race([p1, p2, p3]);
In the above code, as long as one instance of p1, p2 and p3 changes the state first, the state of p will change accordingly. The return value of the Promise instance that changed first is passed to the callback function of p.
const p1 = new Promise((resolve, reject) => { setTimeout(() => { reject('error1'); }, 1000); }) const p2 = new Promise((resolve, reject) => { resolve('value2') }); Promise.race([p1, p2]).then((result) => { console.log('promise.race', result) }).catch((error) => { console.log('promise.race', error) }) // promise.race value2
const p1 = new Promise((resolve, reject) => { reject('error1'); }) const p2 = new Promise((resolve, reject) => { setTimeout(() => { resolve('value2') }, 1000); }); Promise.race([p1, p2]).then((result) => { console.log('promise.race', result) }).catch((error) => { console.log('promise.race', error) }) // promise.race error1
If the promise instance uses catch or then to intercept, then in promise In the result corresponding to race, the value cannot be obtained
const p1 = new Promise((resolve, reject) => { setTimeout(() => { reject('error1'); }, 1000); }) const p2 = new Promise((resolve, reject) => { resolve('value2') }).then((val) => { console.log(val) }) Promise.race([p1, p2]).then((result) => { console.log('promise.race', result) }).catch((error) => { console.log('promise.race', error) }) /** value2 promise.race undefined */
Recognize that promise should be combined with EventLoop
promise itself handles asynchrony, but do you really understand the sequence of code execution?
console.log('promise External 1') const p1 = new Promise((resolve, reject) => { console.log('promise Internal 1') resolve() }).then(() => { console.log('promise then1') }).finally(() => { console.log('promise finally1') }) console.log('promise External 2') const p2 = new Promise((resolve, reject) => { console.log('promise Internal 2') reject() }).catch(() => { console.log('promise catch2') }).finally(() => { console.log('promise finally2') }); console.log('promise External 3') const p3 = new Promise((resolve, reject) => { console.log('promise Internal 3') reject() }).catch(() => { console.log('promise catch3') }).finally(() => { console.log('promise finally3') }); console.log('promise External 4') const p4 = new Promise((resolve, reject) => { console.log('promise Internal 4') resolve() }).then(() => { console.log('promise then4') }).finally(() => { console.log('promise finally4') });
What do you think of the execution order of the above code?
/** promise External 1 promise Internal 1 promise External 2 promise Internal 2 promise External 3 promise Internal 3 promise External 4 promise Internal 4 promise then1 promise catch2 promise catch3 promise then4 promise finally1 promise finally2 promise finally3 promise finally4 */
As can be seen from the above example
- promise's then catch finally belongs to micro tasks in eventLoop, and other codes are executed synchronously from top to bottom.
- Similarly, micro tasks, which are called in a chain, are executed first. For example, finally is always called after then or catch, so all finally belong to the second batch of micro tasks, which are executed later. The first batch of micro tasks are promise then1, promise catch2, promise catch3 and promise then4
- The execution sequence of the same batch of micro tasks is naturally carried out from top to bottom.
Platinum
Maybe you often use promise and have seen the promise specification of es6 before, but do you pay attention to the two latest promise methods? Those who engage in technology always need to keep learning. I believe you are all volume kings!
Promise.allSettled
Promise. The allsettled () method takes a set of promise instances as parameters and wraps them into a new promise instance. The wrapper instance will not end until all of these parameter instances return results, whether fully or rejected. This method was introduced by ES2020.
Once the new promise instance returned by this method is completed, the state is always fully and will not become rejected. After the status changes to full, the parameters received by promise's listening function are an array, and each member corresponds to one passed in promise Promise instance of allsettled().
const p1 = new Promise((resolve, reject) => { resolve('value1') }) const p2 = new Promise((resolve, reject) => { reject('error2') }) const p3 = new Promise((resolve, reject) => { reject('error3') }) const p4 = new Promise((resolve, reject) => { resolve('value4') }) const p = Promise.allSettled([p1,p2,p3,p4]).then((result) => { console.log(result) }) /** [ { status: 'fulfilled', value: 'value1' }, { status: 'rejected', reason: 'error2' }, { status: 'rejected', reason: 'error3' }, { status: 'fulfilled', value: 'value4' } ] */
If the parameter promise instance calls then or catch, promise The value or reason corresponding to allsettled may be undefined
const p1 = new Promise((resolve, reject) => { resolve('value1') }) const p2 = new Promise((resolve, reject) => { reject('error2') }) const p3 = new Promise((resolve, reject) => { reject('error3') }).catch((error) => { console.log(error) }) const p4 = new Promise((resolve, reject) => { resolve('value4') }).then((value) => { console.log(value) }) const p = Promise.allSettled([p1,p2,p3,p4]).then((result) => { console.log(result) }) /** error3 value4 [ { status: 'fulfilled', value: 'value1' }, { status: 'rejected', reason: 'error2' }, { status: 'fulfilled', value: undefined }, { status: 'fulfilled', value: undefined } ] */
Promise.any
Promise. The any () method takes a set of promise instances as parameters and wraps them into a new promise instance.
As long as one of the parameter instances is in the full state, the packaging instance will be in the full state; And return the promise instance whose resolve execution is completed first
const p1 = new Promise((resolve, reject) => { setTimeout(() => { reject('error1'); }, 1000); }) const p2 = new Promise((resolve, reject) => { setTimeout(() => { resolve('value2') }, 2000); }) const p3 = new Promise((resolve, reject) => { resolve('value3') }) Promise.any([p1, p2, p3]).then((result) => { console.log('promise.any', result) }).catch((error) => { console.log('promise.any', error) }) // promise.any value3
If all parameter instances become rejected, the wrapper instance will become rejected.
const p1 = new Promise((resolve, reject) => { setTimeout(() => { reject('error1'); }, 1000); }) const p2 = new Promise((resolve, reject) => { setTimeout(() => { reject('error2') }, 2000); }) const p3 = new Promise((resolve, reject) => { reject('error3') }) Promise.any([p1, p2, p3]).then((result) => { console.log('promise.any', result) }).catch((error) => { console.log('promise.any', error) }) // promise.any AggregateError: All promises were rejected
If promise uses then interception for resolve, promise Any can only get undefined, but it is still in the full state
const p1 = new Promise((resolve, reject) => { setTimeout(() => { resolve('value1'); }, 1000); }).then((res) => { console.log(res) }) const p2 = new Promise((resolve, reject) => { setTimeout(() => { reject('error2') }, 2000); }) const p3 = new Promise((resolve, reject) => { reject('error3') }) Promise.any([p1, p2, p3]).then((result) => { console.log('promise.any', result) }).catch((error) => { console.log('promise.any', error) }) // promise.any undefined
promise and async
Async and promise handle asynchronous functions, but async returns a promise object
If you want to handle success and catch errors, you can write this.
(async () => f())() .then(...) .catch(...)
Diamonds
promise.try
In the daily use of promise, light depends on promise Catch to catch exceptions is not enough, because catch can only catch errors inside promise.
function promiseTest() { console.log(dx) // Synchronization error return new Promise((resolve, reject) => { setTimeout(() => { reject('err1'); //Asynchronous error }, 1000); }) } promiseTest().then((res) => { console.log(res) }).catch((err) => { console.log('p1',err) }) // ReferenceError: dx is not defined
When we call a method, the method may return a promise, but the method will execute, and some synchronization errors may be written outside the promise. Therefore, we have to use the method of try catch.
function promiseTest() { console.log(dx) // Synchronization error return new Promise((resolve, reject) => { setTimeout(() => { reject('err1'); //Asynchronous error }, 1000); }) } try { promiseTest().then((res) => { console.log(res) }).catch((err) => { console.log('p1', err) }) } catch (e) { console.log('synchronization',e) } // Synchronous ReferenceError: dx is not defined
In order to solve the problem that synchronous and asynchronous errors need to be captured separately, promise try
var Promise = require('bluebird'); function promiseTest() { console.log(dx) // Synchronization error return new Promise((resolve, reject) => { setTimeout(() => { reject('err1'); //Asynchronous error }, 1000); }) } Promise.try(() => { return promiseTest() }).catch((e) => { console.log(e) })
Note Promise Try is only a proposal up to now (it was mentioned a long time ago, but there is no following. You need to use Promise library Bluebird, Q, etc., or introduce Polyfill)
Write a minimalist version of promise
// Three statuses: PENDING, full, REJECTED const PENDING = 'PENDING'; const FULFILLED = 'FULFILLED'; const REJECTED = 'REJECTED'; class Promise { constructor(executor) { // The default state is PENDING this.status = PENDING; // Store the value of success status. The default value is undefined this.value = undefined; // Store the value of failure status. The default value is undefined this.reason = undefined; // Calling this method is success let resolve = (value) => { // When the state is PENDING, you can update the state and prevent the executor from calling the two resovle/reject method. if(this.status === PENDING) { this.status = FULFILLED; this.value = value; } } // Calling this method is a failure let reject = (reason) => { // When the state is PENDING, you can update the state and prevent the executor from calling the two resovle/reject method. if(this.status === PENDING) { this.status = REJECTED; this.reason = reason; } } try { // Execute immediately and pass the resolve and reject functions to the user executor(resolve,reject) } catch (error) { // Execute failed logic when exception occurs reject(error) } } // Contains a then method and receives two parameters onFulfilled and onRejected then(onFulfilled, onRejected) { if (this.status === FULFILLED) { onFulfilled(this.value) } if (this.status === REJECTED) { onRejected(this.reason) } } }
Xingyao
Write a promise that can be called in a chain
What does a simple version of promise need to do
- Promise has three states: pending, fulfilled, or rejected; "Specification Promise/A+ 2.1"
- For new promise, you need to pass an executor() executor, which will execute immediately;
- The executor accepts two parameters: resolve and reject;
- The default state of promise is pending;
- Promise has a value to save the value of successful status, which can be undefined/thenable/promise; "Specification Promise/A+ 1.3"
- Promise has a reason value to save the failed state; "Specification Promise/A+ 1.5"
- promise can only change from pending to rejected, or from pending to fully. Once the status is confirmed, it will not change again;
- Promise must have a then method. Then receives two parameters: the successful callback onFulfilled and the failed callback onRejected; "Specification Promise/A+ 2.2"
- If promise is successful when then is called, onFulfilled is executed, and the parameter is the value of promise;
- If promise has failed when then is called, onRejected is executed, and the parameter is reason of promise;
- In what state does the chain call then return then or catch
// For new promise, you need to pass an executor() executor, which will execute immediately; function MyPromise(executor) { // Promise has three states: pending, fulfilled, or rejected; The default state of promise is pending; this.state = 'pending'; // Promise has a value to save the value of successful status, which can be undefined/thenable/promise; this.value; // promise has a reason value to save the failed state; this.reason; this.resolve = (result) => { // Promise is in an irreversible state. If you call the resolve function and reject function at the same time, the result of the first call will be taken by default. if (this.state === 'pending') { this.state = 'fulfilled'; this.value = result } } this.reject = (error) => { // Promise is in an irreversible state. If you call the resolve function and reject function at the same time, the result of the first call will be taken by default. if (this.state === 'pending') { this.state = 'rejected'; this.reason = error } } // promise must have a then method. Then receives two parameters: the successful callback onFulfilled and the failed callback onRejected; this.then = (onFulfilled, onRejected) => { try { // If promise is successful when then is called, onFulfilled is executed, and the parameter is the value of promise; If promise has failed when then is called, onRejected is executed, and the parameter is reason of promise; if (this.state === 'fulfilled') { let result = onFulfilled(this.value) // When then has been called, the value value is restored to undefined. If there is no new promise instance of then next time (this or this), the value obtained is undefined, this.value = undefined // If the result of result is also a MyPromise instance if (result instanceof MyPromise) { return result } } else if (this.state === 'rejected') { let result = onRejected(this.reason) // If the result of result is also a MyPromise instance if (result instanceof MyPromise) { return result } } return { myCatch: this.myCatch, then: this.then, finally: this.finally } } catch { // If the state is rejected and onRejected is not passed, an error will be reported return { myCatch: this.myCatch, then: this.then, finally: this.finally } } } // promise must have a catch method, which is the parameter of the rejected execution function this.myCatch = (onRejected) => { if (this.state === 'rejected') { let result = onRejected(this.reason) this.reason = undefined // If the result of result is also a MyPromise instance if (result instanceof MyPromise) { return result } } return { myCatch: this.myCatch, then: this.then, finally: this.finally } } this.finally = (callback) => { if (this.state !== 'pending') { let result = callback() // If the result of result is also a MyPromise instance if (result instanceof MyPromise) { return result } } // catch returns then when state is full return { myCatch: this.myCatch, then: this.then, finally: this.finally } } try { executor(this.resolve, this.reject) } catch (err) { this.reject(err) } }
Let's test the three cases of reslove, reject and no call
new MyPromise((resolve, reject) => { reject('val1') }).then((val) => { console.log(val) }).then((val) => { console.log(val) }).myCatch((err) => { console.log('Error ', err) return new MyPromise((resolve, reject) => { resolve('2222') }) }).myCatch((err) => { console.log('Error ', err) }).finally(() => { console.log('finally') }).finally(() => { console.log('finally') }).then((res) => { console.log(1, res) }) // Error val1 // finally // finally // 1 2222
new MyPromise((resolve, reject) => { resolve('val1') }).then((val) => { console.log(val) }).then((val) => { console.log(val) }).myCatch((err) => { console.log('Error ', err) return new MyPromise((resolve, reject) => { resolve('2222') }) }).myCatch((err) => { console.log('Error ', err) }).finally(() => { console.log('finally') }).finally(() => { console.log('finally') }).then((res) => { console.log(1, res) }) // val1 // undefined // finally // finally // 1 undefined
new MyPromise((resolve, reject) => { }).then((val) => { console.log(val) }).then((val) => { console.log(val) }).myCatch((err) => { console.log('Error ', err) return new MyPromise((resolve, reject) => { resolve('2222') }) }).myCatch((err) => { console.log('Error ', err) }).finally(() => { console.log('finally') }).finally(() => { console.log('finally') }).then((res) => { console.log(1, res) }) //
King
The above content only simulates some simple functions of promise, does not deal with the asynchrony of promise, and the way to realize chain call is not elegant. In addition, some other APIs of promise are not encapsulated. I think I can only go to Xingyao.
Take a look at the real king and teach you to write promise
https://zhuanlan.zhihu.com/p/183801144
The strongest King
In my heart, technology never has the highest level, and I have higher expectations for the future.
But it's still up to those who can provide es6 promise new api proposals or ideas and be officially adopted