“This is the first day of my participation in the Gwen Challenge in November. See details of the event: The last Gwen Challenge in 2021”.
One, foreword
In the last article, the analysis and processing of various cases of returning value X to realize Promise mainly involve the following points:
- The relevant Promise A+ specification was reviewed;
- According to the specification description and requirements of Promise A+, the core resolution method is implemented: resolvePromise;
In this paper, we continued to improve the Promise and passed the promise-aplus-tests.
Two, questions about current promises
The current version of the Promise source code, has basically realized all the requirements in the Promise A+ specification;
In practice, however, the following two situations are still inconsistent with the native Promise:
- 1. Resolve a Promise directly.
- 2. The then method of a Promise returns a Promise, which in turn resolves a Promise.
note
- Some people might say, “1 and 2 seem to be the same thing.” Resolve returns a promise;
- First, let’s talk about the differences and then analyze them in detail:
- The promise returned directly in resolve corresponds to the value in the source code;
- The promise in resolve in the promise returned in then corresponds to y in the source code;
Resolve a Promise directly
1. Ask questions
If resolve is passed in as a Promise, what happens?
new Promise((resolve,reject)=>{
resolve(new Promise((resolve,reject)=>{
resolve(100)
}))
}).then(data=>{
console.log(data)
})
Copy the code
Resolve (100), then data in the successful callback is 100, so data is a promise, which is 100;
2. Test native Promises
The native Promise returns 100, as expected
3. Test handwritten promises
Returns a DULFILLED success Promise object that behaves differently from the original Promise:
Promise {
state: 'DULFILLED'.value: 100.reason: undefined.onResolvedCallbacks: [].onRejectedCallbacks: []}Copy the code
4. Problem analysis
What’s wrong with the execution of handwritten promises versus native promises?
New Promise((resolve,reject)=>{resolve(new Promise((resolve,reject))=>{resolve(new Promise((resolve,reject))=>{// }). Then (data=>{// This data is a Pending state console.log(data)})Copy the code
Analyze the code execution process:
- Create a promise1 instance with a new Promise, and the Executor1 executor function executes immediately.
- Executor1, executed
resolve(new Promise)
Create an instance of promise2, and execute executor2 immediately. - With Executor2, there are no asynchronous operations, so execution continues
resolve(100)
; - In the resolve method, store value to 100, set the promise2 state to success, and execute a success callback (empty array not collected).
- call
then
Methods; - Promise source code processing: The THEN method creates promise3 internally. Then, promise2 becomes dulfulfilled successfully. Ondepressing is a successful callback, and promise2 is transferred to the then successful callback, that is, data, as data
This results in a DULFILLED Success promise object being returned;
Cause of the problem:
The source code does not take into account the possibility that a value in resolve(value) might be a promise;
const reslove = (value) = >{
if(this.state === PENDING){
this.value = value // Value can be a promise
this.state = DULFILLED;
this.onResolvedCallbacks.forEach(fn= >fn())
}
}
Copy the code
Solution:
Add a judgment, if value is a promise, call then to make it perform;
5. Code implementation
Determine if value is a Promise (it must be its own Promise), call then, and pass resolve, reject (Success will invoke resolve; Reject is called on failure, returning the execution of the final promise;
const reslove = (value) = > {
// Value is a self-implemented Promise that calls then and returns the final result
if(value instanceof Promise) {return value.then(reslove, reject)
}
if (this.state === PENDING) {
this.value = value
this.state = DULFILLED;
this.onResolvedCallbacks.forEach(fn= > fn())
}
}
Copy the code
In this way, when reslove makes a promise, its then method is called in the source reslove method, which returns the result of the promise execution.
Note: This situation is not explained in the Promise A+ specification;
Then return a promise, resolve a promise
1. Ask questions
In the previous article, when promise.then returns a Promise, the core logic resolvePromise looks like this:
function resolvePromise(promise2, x, resolve, reject) {
if (promise2 === x) {
return reject(new TypeError('Error occurred'))}if ((typeof x === 'object'&& x ! = =null) | |typeof x === 'function') {
try {
let then = x.then;
if(typeof then === 'function') {// Call promise's then method
then.call(x, y= > {
resolve(y) // Update the status of promise2
}, r= > {
reject(r)
});
}else{
resolve(x)
}
} catch(e) { reject(e); }}else {
resolve(x)
}
}
Copy the code
- The return value y of the method in then may be a promise, forming a nested promise;
Case 1 already dealt with the resolve promise case, is there still a problem here?
This is usually fine, but the promise here could be a promise that someone else fulfilled; (In case 1, the Promise must be self-implemented)
2. Test native Promises
let promise2 = new Promise((resolve, reject) = > {
resolve(200)
}).then(data= > {
return new Promise((resolve, reject) = > {
setTimeout(() = >{
resolve(new Promise((resolve, reject) = > {
setTimeout(() = >{
resolve(data)
}, 1000)}})),1000)
})
})
promise2.then(data= > {
console.log(data)
})
/ / 200
Copy the code
3. Test handwritten promises
Promise {state: 'PENDING', value: undefined, reason: undefined, onResolvedCallbacks: [], onRejectedCallbacks: []}Copy the code
4. Problem analysis
let p = new Promise((resolve, reject) = > {
// 1, the executor function is executed immediately
resolve(200)
}).then(data= > {
// 2, enter the successful callback processing, return Promise
return new Promise((resolve, reject) = > {
setTimeout(() = >{
resolve(new Promise((resolve, reject) = > {
setTimeout(() = >{
resolve(data)
}, 1000)}})),1000)
})
})
p.then(data= > {
console.log(data)
})
Copy the code
Analyze the code execution process:
- The new Promise creates the instance, the executor executor function is executed immediately,
resolve(200)
Be performed; - The resolve method stores a value of 200, sets the Promise state to success, and executes a success callback (empty array not collected).
- call
then
Methods; - Promise source code processing: Then internally create the promise2-1 method. When P is dul-filled, use setTimeout to create macro task 1 (obtain the ondepressing return value x, and call resolvePromise to resolve the return value X). Deferred to the next event loop;
- perform
p.then
; - The state of the last macro task has not yet been executed, so the state of the last macro task is still PENDING, so the state of the last macro task is still PENDING
p.then
The success/failure callback function in the - At this point, the code is done, that is, the first round of macro tasks is done;
- Macro task 1 is executed, entering the successful processing of the first THEN in the code, data is 200, and the return value is a P
Promise;
- Promise source code processing: internally get then return value x, call resolvePromise for unified parsing, because here x is a Promise, so, in resolvePromise will call its then method;
- However, because the promise (x) has an asynchronous operation (setTimeout 1 second), when the then method of the promise (x) is called, the state of the promise (x) is still PENDING
- One second later, execute resolve(new Promise), at which point the Promise in resolve is the y in the source code;
Question why
- The hand-written source code deals only with the return value x of the method in then; But resolve is a promise; (Resolve promise is the y in the source code)
- Since y is a promise, resolve(y), or data in THEN, yields a PENDING promise instance.
The solution
- Since Y may be a promise object, resolvePromise is used for recursive processing of Y until y is an ordinary value.
Related Promise A+ specification contents:
5. Code implementation
function resolvePromise(promise2, x, resolve, reject) {
if (promise2 === x) {
return reject(new TypeError('Error occurred'))}if ((typeof x === 'object'&& x ! = =null) | |typeof x === 'function') {
try {
let then = x.then;
if(typeof then === 'function'){
then.call(x, y= > {
// resolve(y)
resolvePromise(promise2, y, resolve, reject) // handle y recursively
}, r= > {
reject(r)
});
}else{
resolve(x)
}
} catch(e) { reject(e); }}else {
resolve(x)
}
}
Copy the code
Three, the compatible processing of Promise
In development, the promises used are not necessarily native or implemented by us;
It is possible for any framework/library/developer to implement A promise according to the Promise A+ specification;
To ensure that using these promises does not cause project problems, promises need to be handled in a compatible manner;
- note
Theoretically, A Promise implemented strictly in accordance with the Promise A+ specification does not need compatibility processing; Therefore, compatibility processing is mainly used to make up for the Promise loophole and imrigor in implementation;
- Such as:
let Promise1; // Self-fulfilling Promise
letPromise2;// A Promise that someone else fulfilled
promise = new Promise1((resolve, reject) = > {
resolve(1);
}).then(() = > {
return new Promise2();
})
Copy the code
Promise compatible processing, there are the following:
- The state of each Promise instance can be changed only once;
4. Promise-aplus-tests
Promise-aplus-tests is used to test whether the promise implemented by oneself conforms to the Promise A+ specification.
1, install promise-aplus-tests
npm install promises-aplus-tests -g
Copy the code
2, add test entry – create delay object
- Creating a delay object
Promise.deferred
Deferred object: an object that has a “deferred” effect (because it contains promises);
The promise.deferred method is used to test whether the Promise implementation on the current DFD object complies with the Promise A+ specification.
Promise.deferred = function(){
let dfd = {}
dfd.promise = new Promise((resolve,reject) = >{
dfd.resolve = resolve;
dfd.reject = reject;
})
return dfd;
}
Copy the code
The use of deferred objects reduces one layer of nesting;
- in
Promise.deferred
Method, you create a Promise objectdfd.promise
; - Equivalent to binding resolve/reject to
dfd.resolve
/dfd.reject
; - The internal of the Deferred is called when a promise succeeds
dfd.resolve
; Failure to invokedfd.reject
;
What it does: Migrates the Promise method to DFD objects, eliminating one layer of nesting through direct object access;
See an example of the effect of a delay object:
- Do not use delay functions: need to nest a layer
new Promise
function test() {
return new Promise((resolve, reject) = > {
setTimeout(() = > {
resolve(200)},1000); })}Copy the code
- Use delay functions: No nesting required
new Promise
function test() {
let dfd = Promise.deferred();
setTimeout(() = > {
dfd.resolve(200);
}, 1000);
return dfd.promise;
}
Copy the code
Conclusion:
- Compared with the two methods of code, the use of delay functions significantly reduces the nesting of new Promises.
- Each call
Promise.deferred()
You immediately get a brand new promise instance; - Deferred objects, which are “deferred” processing of deferred objects that contain promises;
3. Perform tests
Promises - Aplus - Tests XXX (Promise entry file path)Copy the code
Passed the Promise A+ specification for 872 test cases;
Five, the end
In this article, the source code of Promise was improved and tested through promise-aplus-tests. The main points are as follows:
- Improve the Promise source code: support two nested Promise cases;
- Analyze the Promise implementation process;
- Create deferred objects and test with promise-aplus-tests;
Continue to implement promise.resolve and promise.reject;
Maintain a log
- 20211102
- Added instructions and examples for the “delay function section”;
- Added support for two “nested return promises”;
- Optimized the description of Promise execution process analysis;
- Readjusted the first and second level contents, ending and abstract;
- 20211115
- Correct typos;