This article realizes a simple class to understand the Promise principle, compares Promise with traditional callback, shows Promise’s advantages in asynchronous programming, and finally introduces Promise’s application in actual development.
A concept.
Promise decouples asynchronous operations from callbacks and associates them with execution state. Promise is notified of the status after the asynchronous operation, and the Promise is responsible for triggering the callback function.
Ii. The Promise Principle
1. Status changes
Input:
let p0 = new Promise((resolve, reject) = > {})
console.log('p0', p0)
let p1 = new Promise((resolve, reject) = > {
resolve('success')})console.log('p1', p1)
let p2 = new Promise((resolve, reject) = > {
reject('failure')})console.log('p2', p2)
let p3 = new Promise((resolve, reject) = > {
throw('error')})console.log('p3', p3)
Copy the code
Output:
Conclusion:
- The newly created Promise object is in a pending state.
- After the asynchronous operation is complete, call resolve/ Reject to change the status to fulfilled/ Rejected.
- When an exception (synchronization exception) occurs during the execution of the function, change the status to Rejected.
Implementation:
class MyPromise {
constructor(executor) {
this.initValue();
// Since resolve/reject is called by the external executor function, this must be hard-bound to the current MyPromise object
this.initBind();
try {
// Execute the function passed in
executor(this.resolve, this.reject);
} catch (e) {
Execute reject when an error is caught
this.reject(e); }}initBind() {
this.resolve = this.resolve.bind(this);
this.reject = this.reject.bind(this);
}
initValue() {
this.PromiseResult = null;
this.PromiseState = 'pending';
}
resolve(value) {
// The status can only be changed from pending to pity/Rejected
if (this.PromiseState == 'pending') {this.PromiseState = 'fulfilled';
this.PromiseResult = value; }}reject(reason) {
// The status can only be changed from pending to pity/Rejected
if (this.PromiseState ! = ='pending') {this.PromiseState = 'rejected';
this.PromiseResult = reason; }}}Copy the code
2. Execute the callback
Input/output:
// Immediately print "resolve= success"
const p1 = new Promise((resolve, reject) = > {
resolve('success');
}).then(res= > console.log('resolve=',res), err= > console.log('reject=',err))
Reject = reject after 1 second
const p2 = new Promise((resolve, reject) = > {
setTimeout(() = > {
reject('failure');
}, 1000)
}).then(res= > console.log('resolve=',res), err= > console.log('reject=',err))
Copy the code
Conclusion:
- Then takes two arguments:
Success callback
andFailure callback
- When the Promise state is
fulfilled
performSuccess callback
forrejected
performFailure callback
- You can do this by calling then multiple times
Register multiple callback functions
, pay attention to the distinction between chain calls
Implementation:
When registering a callback with THEN, if the Promise state is fulfilled/ Rejected, the callback function will be executed.
If the Promise state is pending, the callback function is saved and executed after the asynchronous operation ends.
initValue(){...// Save the callback function.
this.onFulfilledCallbacks = [];
this.onRejectedCallbacks = [];
}
resolve(value){...// The status changes to depressing, which executes the successful callback of saving
while (this.onFulfilledCallbacks.length) {
this.onFulfilledCallbacks.shift()(this.PromiseResult); }}reject(reason){...// The status changes to Rejected, which executes the saved failed callback
while (this.onRejectedCallbacks.length) {
this.onRejectedCallbacks.shift()(this.PromiseResult); }}then(onFulfilled, onRejected) {
// Make sure it is a function
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : val= > val;
onRejected = typeof onRejected === 'function' ? onRejected : reason= > { throw reason };
if (this.PromiseState === 'fulfilled') {
// Perform a big callback
onFulfilled(this.PromiseResult);
} else if (this.PromiseState === 'rejected') {
// Execute the Rejected callback
onRejected(this.PromiseResult);
}else if (this.PromiseState === 'pending') {
// Promise is in a pending state and holds two callbacks temporarily
this.onFulfilledCallbacks.push(onFulfilled);
this.onRejectedCallbacks.push(onRejected); }}Copy the code
3. Chain call
Input/output
// the chain-call output is 200
const p3 = new Promise((resolve, reject) = > {
resolve(100);
}).then(res= > 2 * res)
.then(res= > console.log(res))
// the chain-call output is 300
const p4 = new Promise((resolve, reject) = > {
resolve(100);
}).then(res= > new Promise((resolve, reject) = > resolve(3 * res)))
.then(res= > console.log(res))
Copy the code
conclusion
-
The then method itself returns a new Promise object.
-
If the callback function returns a Promise object, the state of the new Promise object is determined by that Promise object.
-
If the callback function returns a value other than a Promise object, the new Promise object is in a successful state.
implementation
then(onFulfilled, onRejected){...var thenPromise = new MyPromise((resolve, reject) = > {
const resolvePromise = cb= > {
try {
const x = cb(this.PromiseResult)
if(x === thenPromise){
// wait for yourself to complete, loop waiting: then returns the value of then in the then callback.
reject(new TypeError('Chaining cycle detected for promise'));
}
if (x instanceof MyPromise) {
// If the return value is a Promise object, the new Promise state is determined by that Promise.
x.then(resolve, reject);
} else {
// Either Promise will succeedresolve(x); }}catch (err) {
// Handle an errorreject(err); }}if (this.PromiseState === 'fulfilled') {
// If the current state is successful, execute the first callback
resolvePromise(onFulfilled);
} else if (this.PromiseState === 'rejected') {
// If the current state is failed, perform the second callback
resolvePromise(onRejected);
} else if (this.PromiseState === 'pending') {
// If the state is pending, save two callbacks for now
this.onFulfilledCallback = resolvePromise(onFulfilled);
this.onRejectedCallback = resolvePromise(onRejected); }})// Return the wrapped Promise
return thenPromise;
}
Copy the code
4. Call timing
Input and output
setTimeout(() = >{console.log(0)},0);
const p = new Promise((resolve, reject) = > {
console.log(1);
resolve()
}).then(() = > console.log(2))
console.log(3)
// The output order is 1, 3, 2, 0
Copy the code
conclusion
- Even if the state of the Promise is immediately updated as fulfilled, the callback function will not be executed immediately.
- The callback function is executed in the microtask of the event loop.
- Because callbacks are inherently asynchronous, placing them on microtasks allows later synchronized code to be executed as quickly as possible.
implementation
const resolvePromise = cb= > {
setTimeout(() = > {
// Execute callback...})}Copy the code
Promise vs. traditional callback
1. Callback hell
In a scenario where multiple asynchronous operations are performed, traditional callbacks need to pass in callback functions as arguments, causing the problem of “callback hell”. Promise decouples the asynchronous operation from the callback function, eliminating the need to pass in the callback function in the first place.
// Traditional callbacks implement multiple asynchronous operations
doSomething(function(result) {
doSomethingElse(result, function(newResult) {
doThirdThing(newResult, function(finalResult) {
console.log('Got the final result: ' + finalResult);
}, failureCallback);
}, failureCallback);
}, failureCallback);
// Promise implements multiple asynchronous operations
doSomething().then(function(result) {
return doSomethingElse(result);
})
.then(function(newResult) {
return doThirdThing(newResult);
})
.then(function(finalResult) {
console.log('Got the final result: ' + finalResult);
})
.catch(failureCallback);
Copy the code
2. Catch exceptions
Note: Exceptions here refer to callback function exceptions, not asynchronous operation exceptions.
// the following is a typical timeoutCallback. Because timeoutCallback is called in an asynchronous operation, try catch cannot catch the asynchronous exception.
try{
setTimeout(() = >timeoutCallback("3 seconds passed"), 3000);
}catch(err){
// The timeoutCallback exception cannot be caught.
console.log(err);
}
// Promise is responsible for calling the callback. When the asynchronous operation ends and the Promise state changes, the Promise invokes the callback with a try catch.
const wait = ms= > new Promise(resolve= > setTimeout(resolve, ms));
wait(3000).then(() = > timeoutCallback("3 seconds passed")).catch(err= >{console.log(err)});
Copy the code
Iv. Promise application
1. Create completed promises
PromiseAnd the resolve ()Promise.reject()
Copy the code
2. Encapsulate traditional callbacks
Since traditional callbacks are plagued by callback hell and lack of asynchronous exception catching, promises are used to encapsulate existing traditional callbacks.
The following is a typical timeoutCallback. Because the timeoutCallback is executed asynchronously, the exception in the asynchronous callback cannot be caught.
try{
setTimeout(() = >timeoutCallback("3 seconds passed"), 3000);
}catch(err){
// The timeoutCallback exception cannot be caught.
console.log(err);
}
// Promise can separate the asynchronous operation from the callback and automatically add a try catch to the callback function when the callback is called after the asynchronous operation completes.
const wait = ms= > new Promise(resolve= > setTimeout(resolve, ms));
wait(3000).then(() = > timeoutCallback("3 seconds passed")).catch(err= >{console.log(err)});
Copy the code
3. Perform multiple asynchronous operations
// Parallel multiple asynchronous operations and wait until all operations are complete to proceed to the next operation
Promise.all([promise1, promise2, promise3])
.then(([result1, result2, result3]) = > {
// next step
});
.catch(err){
Reject (reject); reject (reject); reject (reject);
}
// Perform multiple asynchronous operations in sequence (chain calls)
[promise1, promise2, promise3].reduce((pre, cur) = > pre.then(() = >{return cur}), Promise.resolve())
.then(result3= > {
/* use result3 */
}).catch(err= >{
Reject (reject); reject (reject); reject (reject);
});
// In ES2017, the above code can also be optimized with async/await
let result;
try{
for (const pro of [promise1, promise1, promise1]) {
result = awaitpro(result); }}catch(err){
result = err;
}
Copy the code
reference
Read it, write it by hand, the most accessible version of the Promise principle
Use promises to encapsulate the worker