Small knowledge, big challenge! This article is participating in the creation activity of “Essential Tips for Programmers”.
This article has participated in the “Digitalstar Project” and won a creative gift package to challenge the creative incentive money.
One, foreword
In the previous article, we implemented Promise’s support for asynchronous operations, mainly covering the following points:
- Test Promise’s support for asynchronous operations;
- Analyze current Promise code issues and solutions;
- Use publish and subscribe idea to support asynchronous operation.
- Promise tests for asynchronous operations;
In this article, we’ll continue to implement the chain call of Promise;
Two, the previous review
In the first few articles, I briefly introduced the use of promises; Translate Promise A+ specification; Implement simplified Promise;
In the previous article, we implemented Promise’s support for asynchronous operations using the publish-subscribe model in the following two steps:
- Collect the success/failure callbacks from the then method first;
- When the Promise state changes (resolve or Reject is called), the corresponding callback processing is executed in turn.
Third, ask questions
A core feature of Promise is its support for chained calls;
Thus, using promises is a good way to solve the “callback hell” of asynchronous logic;
Remark:
- There are multiple return value types that need to be handled with respect to the return value of the THEN function (that is, x as mentioned in the Promise A+ specification), which will be covered in more detail in the next article;
- The purpose of this article is to realize the Promise chain call function. Ignore the case where the return value is PROMISE;
1. Test the native Promise
- The so-called “ordinary value” needs to meet the following requirements:
- Can’t be a Promise object;
- Cannot report an error or throw an exception, must be able to return normally; Throw new Error(), for example, is not a normal value;
- Successful or failed callback processing does not return a value, equivalent to returning undefined, which is a normal value;
Case 1, in a successful callback, returns normal
let promise = new Promise((resolve, reject) = > {
setTimeout(() = > { // Asynchronous operation
resolve('Executed successfully'); // Enter successful callback processing
}, 1000)
})
promise.then((value) = > {
console.log('[then1-onFulfilled]', value)
return "then1-onFulfilled:" + value
// return new Error() // Returns a normal value
// return undefined // Returns the normal value
}, (reson) = > {
console.log('[then1-onRejected]', reson)
}).then((value) = > {
console.log('[then2-onFulfilled]', value)
}, (reson) = > {
console.log('[then2-onRejected]', reson)
})
// [then1-ondepressing] This is a big pity
// [then2-ondepressing] then1-ondepressing: this is a big pity
Copy the code
- Phenomenon:
- 1 second later, the ondepressing callback in then1 will be performed and return value will be continued.
- Then, enter the ondepressing callback of THEN2, and the value transmitted by THEN1 can be acquired.
- Conclusion:
- The success callback returns a normal value, which goes to the success callback of the next THEN;
Case 2, in a failed callback, returns the normal value
let promise = new Promise((resolve, reject) = > {
setTimeout(() = > {
reject("Execution failed") // Enter failed callback processing
}, 1000)
})
promise.then((value) = > {
console.log('[then1-onFulfilled]', value)
return "then1-onFulfilled:" + value
}, (reson) = > {
console.log('[then1-onRejected]', reson)
return "then1-onRejected:" + reson // Return the normal value
// return new Error() // Returns a normal value
// return undefined // Returns the normal value
}).then((value) = > {
console.log('[then2-onFulfilled]', value)
}, (reson) = > {
console.log('[then2-onRejected]', reson)
})
// [then1-onRejected] The command is executed failed
// [then2-onfulfilled] then1-onfulfilled: this is a big pity
Copy the code
-
Phenomenon:
- 1 second later, the onRejected callback of then1 is executed and return reson;
- Then, enter the ondepressing callback of THEN2, and the value transmitted by THEN1 can be acquired. (The value is the normal value for then1 return.)
-
Conclusion:
- A failure callback returns a normal value, which goes to the success callback of the next THEN;
-
Remark:
- Since the state of each Promise instance can only be modified once, and the failed promise in the example enters a successful callback in the second THEN, it indicates that a new promise instance is returned after then1 processing is completed.
Case 3, a successful callback, throws an exception
let promise = new Promise((resolve, reject) = > {
setTimeout(() = > {
resolve("Executed successfully")},1000)
})
promise.then((value) = > {
console.log('[then1-onFulfilled]', value)
throw new Error("Throw exception")},(reson) = > {
console.log('[then1-onRejected]', reson)
return "then1-onRejected:" + reson
}).then((value) = > {
console.log('[then2-onFulfilled]', value)
}, (reson) = > {
console.log('[then2-onRejected]', reson)
})
// [then1-ondepressing] This is a big pity
// [then2-onRejected] Error: Throws an exception
Copy the code
- Phenomenon:
- 1 second later, the Ondepressing callback in Then1 is executed and an exception is thrown;
- Then, enter then2’s onRejected callback and catch the exception thrown by THEN1.
- Conclusion:
- A successful callback throws an exception that goes into the next failed callback for then;
Case 4, in a failed callback, an exception is thrown
let promise = new Promise((resolve, reject) = > {
setTimeout(() = > {
reject("Execution failed")},1000)
})
promise.then((value) = > {
console.log('[then1-onFulfilled]', value)
}, (reson) = > {
console.log('[then1-onRejected]', reson)
throw new Error("Throw exception")
}).then((value) = > {
console.log('[then2-onFulfilled]', value)
}, (reson) = > {
console.log('[then2-onRejected]', reson)
})
// [then1-onRejected] The command is executed failed
// [then2-onRejected] Error: Throws an exception
Copy the code
- Phenomenon:
- After 1 second, the onRejected callback of then1 is executed and an exception is thrown.
- Then, enter then2’s onRejected callback and catch the exception thrown by THEN1.
- Conclusion:
- A failed callback throws an exception that goes to the next then failed callback;
Case 5, an error is reported in the executor executor
If an exception is thrown in executor, resolve and reject have not been executed:
Note: Synchronization logic error, non – asynchronous operation.
let promise = new Promise((resolve, reject) = > {
throw new Error("Executor synchronization error") // Throw an exception
resolve("Processed successfully") // Will not be executed
})
promise.then((value) = > {
console.log('[then1-onFulfilled]', value)
}, (reson) = > {
console.log('[then1-onRejected]', reson)
}).then((value) = > {
console.log('[then2-onFulfilled]', value)
}, (reson) = > {
console.log('[then2-onRejected]', reson)
})
// [then1-onRejected] Error: Executor synchronization fails
// [then2-onFulfilled] undefined
Copy the code
- Phenomenon:
- The synchronization operation in the Executor executor function throws an exception into then1’s failure callback;
- Since then1 does not return, it is equivalent to returning the normal value of undefined, and entering then2’s successful processing;
- Conclusion:
- Executor synchronization logic throws an exception and then fails processing;
conclusion
Summarize according to the above five situations:
- A chained call to a Promise that, when called then, returns a brand new Promise instance;
- The method (success or failure) in then returns a normal value, which is the result of the success of the next THEN.
- Methods in the executor function and then (whether successful or unsuccessful) throw exceptions that enter the failure result of the next THEN;
Remark:
- If the then method returns an instance of a Promise, the successful or failed callback will be executed based on the state of the promise:
- Successful promise: a successful callback that passes a return value to the next THEN;
- Failed promises: failed callbacks that pass the cause of failure to the next THEN;
2. Test handwritten promises
Chain call example (same as testing native Promise code) :
let promise = new Promise((resolve, reject) = > {
setTimeout(() = > {
resolve('Executed successfully');
}, 1000)
})
promise.then((value) = > {
console.log('[then1-onFulfilled]', value)
return "then1-onFulfilled:" + value
}, (reson) = > {
console.log('[then1-onRejected]', reson)
}).then((value) = > {
console.log('[then2-onFulfilled]', value)
}, (reson) = > {
console.log('[then2-onRejected]', reson)
})
// TypeError: Cannot read property 'then' of undefined
Copy the code
The phenomenon of
- Throws an exception at the second then method call;
3. Problem analysis
- Phenomenon: handwritten Promise source code, currently only support a single call then method, multiple calls will report an error;
- Cause: The current Promise does not return a new Primise object after calling the then method, so it cannot continue
.then
;
class Promise{
constructor(executor){
this.state = PENDING;
this.value = undefined;
this.reason = undefined;
this.onResolvedCallbacks = [];
this.onRejectedCallbacks = [];
const reslove = (value) = >{... }const reject = (reason) = >{... } executor(reslove, reject); }then(onFulfilled, onRejected){
if(this.state === PENDING){
this.onResolvedCallbacks.push(() = >{
onFulfilled(this.value) // No new Promise is returned after being called
})
this.onRejectedCallbacks.push(() = >{
onRejected(this.value) // No new Promise is returned after being called})}if(this.state === DULFILLED){
onFulfilled(this.value) // No new Promise is returned after being called
}
if(this.state === REJECTED){
onRejected(this.reason) // No new Promise is returned after being called
}
// Then does not continue to return a promise object, so.then cannot continue}}module.exports = Promise;
Copy the code
4. Solutions
After the callback function of the THEN method completes, the return value of the function needs to be retrieved and wrapped as a new Promise instance.
- Gets the return value of the THEN callback processing; (The return value is the Promise A+ x from the specification)
- Packaging becomes a brand new promise instance;
- Exception handling: Add try.. to executor and then methods catch… To catch exceptions;
Four, the implementation of the Promise chain call
1. Get the return value of the THEN callback processing
class Promise{
constructor(executor){... }then(onFulfilled, onRejected){
if(this.state === DULFILLED){
// Get the return value of the callback processing
let x = onFulfilled(this.value)
}
if(this.state === REJECTED){
let x = onRejected(this.reason)
resolve(x)
}
if(this.state === PENDING){
this.onResolvedCallbacks.push(() = >{
let x = onFulfilled(this.value)
resolve(x)
})
this.onRejectedCallbacks.push(() = >{
let x = onRejected(this.reason)
resolve(x)
})
}
// Wrap the return value x from the callback processing into a brand new Promise instance
let promise2 = new Promise((resolve, reject) = >{
// Problem: can't get x, not in scope
});
// Returns a new promise instance so that the external can continue calling.then
returnpromise2; }}Copy the code
The problem
- The then function returns the newly created promise2, but there is no return value x inside of promise2.
2, then returns a new promise
To solve this problem, return x in the promise2 executor executor;
class Promise{
constructor(executor){... }then(onFulfilled, onRejected){
// Gets the return value of the success/failure callback and wraps it as a new Promise instance
let promise2 = new Promise((resolve, reject) = >{
/ / synchronize
if(this.state === DULFILLED){
// Get the return value of the callback processing
let x = onFulfilled(this.value)
// Change the state of promise2 and return the new Promise instance (primise2)
resolve(x)
}
/ / synchronize
if(this.state === REJECTED){
let x = onRejected(this.reason)
resolve(x)
}
/ / asynchronous
if(this.state === PENDING){
this.onResolvedCallbacks.push(() = >{
let x = onFulfilled(this.value)
resolve(x)
})
this.onRejectedCallbacks.push(() = >{
let x = onRejected(this.reason)
resolve(x)
})
}
});
// Return the new Promise instance
returnpromise2; }}Copy the code
3. Handling of exceptions
Executor executor functions and methods (whether successful or unsuccessful) in THEN throw exceptions that enter the failure result of the next THEN;
Simply catch the exception in the following places and reject:
- Executor executor functions (implemented in the Promsie version)
- Synchronized operations (DULFILLED and REJECTED) in the then method, when the callback function is executed;
- An asynchronous operation (PENDING) in the then method, while the callback function is executing;
class Promise{
constructor(executor){
const reslove = (value) = >{... }const reject = (reason) = >{... }try{
executor(reslove, reject);
}catch(e){
reject(e) // Catch an exception}}then(onFulfilled, onRejected){
let promise2 = new Promise((resolve, reject) = >{
if(this.state === DULFILLED){
try{
let x = onFulfilled(this.value)
resolve(x)
}catch(e){
reject(e) // Catch an exception}}if(this.state === REJECTED){
try{
let x = onRejected(this.reason)
resolve(x)
}catch(e){
reject(e) // Catch an exception}}if(this.state === PENDING){
this.onResolvedCallbacks.push(() = >{
try{
let x = onFulfilled(this.value)
resolve(x)
}catch(e){
reject(e) // Catch an exception}})this.onRejectedCallbacks.push(() = >{
try{
let x = onRejected(this.reason)
resolve(x)
}catch(e){
reject(e) // Catch an exception}}}}));returnpromise2; }}Copy the code
4. Test the chain call of handwritten Promsie
let promise = new Promise((resolve, reject) = > {
setTimeout(() = > { // Asynchronous operation
resolve('Executed successfully'); // Successful state, pass the parameter
}, 1000)
})
promise.then((value) = > {
console.log('[then1-onFulfilled]', value)
return "then1-onFulfilled:" + value
}, (reson) = > {
console.log('[then1-onRejected]', reson)
}).then((value) = > {
console.log('[then2-onFulfilled]', value)
}, (reson) = > {
console.log('[then2-onRejected]', reson)
})
// [then1-ondepressing] This is a big pity
// [then2-ondepressing] then1-ondepressing: this is a big pity
Copy the code
Summary: 5 cases are consistent with the original Promise;
Five, the execution process analysis
Analyze the current Promise execution process:
- First, when the application layer new Promise creates a Promise instance, the incoming executor executor is executed immediately from constructor;
- Application layer call
promise.then
Pass in the success/failure state processing logic. - Then processing, in the source code, creates a promise2 instance; The promise2 executor executor is also executed immediately;
- The promise-handling of the three states is now wrapped in the promise2 executor. Therefore, execution will not be affected, will be executed immediately;
- This way, the return value x is available in the promise2 wrapper for then return value X!
- When resolve(x) is called by promise2, it goes outside
promise.then
The successful callback is processed and returns a normal value; - Promise2 is available in the source code
promise.then
Returns the value x, and updates the status of promise2 to a successful state by reslove(x). - Then, we enter the application layer and the Promise chain calls the success callback of the second THEN…
Remark:
- There are recursive operations: the callback processing in the then method returns a promise, which continues then, which continues to return a promise…
Six, the end
This article mainly realized the chain call function of Promise, mainly involving the following points:
- Five cases of Promise chain call, return normal value and throw exception are introduced.
- The problems and solutions of current Promise source code are analyzed.
- Implementation, functional test and execution process analysis of chain call of Promise;
Remark:
- Because the return value x is promise is not currently considered, the code is not final;
- The next article focuses on the return value X, which implements the core method in the Promise source code:
resolvePromise
;
Next, implement the processing of the Promise return value x;
Maintain a log
- 20211031
- Added 5 cases and summary of native Promise chain calls;
- Added a note for the Promsie exception handling section;
- The content of “execution process analysis” was added to explain the changes after embedding promise 2 in THEN;
- Reorganize the first and second level catalogs, add necessary remarks and code comments, modify typos, modify article abstracts;
- Todo association Promise A+ specification content;
- 20211101
- Analysis and implementation of TOdo complementary THEN penetration;