preface
A basic promise that handles synchronization states was completed in the previous post, so if you haven’t seen it, click on the link below.
Complete A+ specification Promise in three steps, one comment per line of code on average (part 1)
Asynchronous processing is done here with Promise 1.5, and Promise 2.0 does the chain-calling and the resolvePromise that handles the then return value
The final conclusion is the real essence, which is my written description of the promise principle after watching the video for at least five times and reviewing my code countless times.
1.5 transition myPromise
Before moving on to Promise 2.0, I thought I might need a 1.5 as a transition, specifically for asynchronous processing, which promises handle in a publish-subscribe mode.
This section is relatively simple, directly into the code, all the improvements over 1.0 have comments, the key is to understand the idea.
const RESOLVED = 'RESOLVED'; / / success
const REJECTED = 'REJECTED'; / / fail
const PENDING = 'PENDING'; / / wait state
class Promise {
constructor(executor) {
this.status = PENDING;
this.value = undefined;
this.reason = undefined;
// Used to store successful callbacks
this.onResolvedCallbacks = [];
// Since promise can be multiple times, then receives multiple callbacks, so it uses the stack form
this.onRejectedCallbacks = [];
let resolve = (value) = > {
if(this.status === PENDING){
this.value = value;
this.status = RESOLVED;
// Executor calls resolve to execute a callback stored in the stack
this.onResolvedCallbacks.forEach(fn= >fn()); }}let reject = (reason) = > {
if(this.status === PENDING){
this.reason = reason;
this.status = REJECTED;
this.onRejectedCallbacks.forEach(fn= >fn()); }}try{
executor(resolve,reject);
}catch(e){
reject(e);
}
}
then(onFulfilled,onRejected){
if(this.status === RESOLVED){
onFulfilled(this.value);
}
if(this.status === REJECTED ){
onRejected(this.reason)
}
if(this.status === PENDING){// The promise state is still pending, indicating that the executor is asynchronous
this.onResolvedCallbacks.push((a)= >{// When asynchronous, save the ondepressing function passed in by the user (closure)
// Some operations will be performed before the ondepressing passed in by the user (which will be added in the next version)
onFulfilled(this.value);
});
/ / here cannot directly this. OnResolvedCallbacks. Push (onFulfilled ()) onFulfilled will be implemented immediately
/ / didn't use this. OnResolvedCallbacks. Push (onFulfilled) although not direct execution, is not convenient to expand
this.onRejectedCallbacks.push((a)= >{
onRejected(this.reason); }); }}}module.exports = Promise
Copy the code
To summarize
In the asynchronous case, the promise itself (or the executor passed in by the user within the promise) is executed immediately, but the result of the executor function is returned asynchronously.
The then method is executed synchronously, so the THEN method is always executed before the executor, so the methods passed in the THEN method are stored for the executor callback.
In other words, in the asynchronous case, the promise state is uncertain. First execute the THEN, store the onFulfilled/onRejected function in the THEN, and then supply the executor, which will call the promise after confirming the state according to the result returned by the asyncio.
In ‘MyPromise1.0’, the executor is executed, the promise state is determined, and then the state is used to determine which function is passed in by the user. This is the opposite of how asynchrony was handled in 1.5.
2.0 introduce myPromise
Version 2.0 of myPromise addresses two major challenges to implementing promises:
-
Chain calls
-
Then three kinds of return value processing
As in the previous installment, let’s take a look at the promise features in Node for a few examples.
then
The return value of theonFulfilled
oronRejected
Is passed to the next one.then
theonFulfilled
. That’s the chain call
read('./na1me.txt').then((data) = > {// This is a deliberate error into the first layer, reject
// A normal return in first resolve is accepted by then's resolve at the next level
return 'then1 [resolve] return.'
},(err) => {
// A normal return in the first reject layer is accepted by the resolve of the next then layer.
return 'then1 [reject] return.'
}).then((data) = > {
console.log(data,'then2 [resolve] worked.');// The result is called here
},(err) => {err
console.log(err,'then2 [reject] worked.');
})
Copy the code
- The value returned by the callback then is not the same as the value returned by the callback
then
theonFulfilled
), error value (pass to the nextthen
theonRejected
),promise
(Execute this firstpromise
And based on thispromise
The return value of thethen
theonFulfilled
oronRejected
) to be treated separately. Implement the resolvePromise function for processing
2.1 Return error value
read('./na1me.txt').then((data) = > {// there is a deliberate error in reject
throw new Error('then1 [resolve] throw error.')// An error in resolve is accepted by then reject at the next level
},(err) => {
throw new Error('then1 [reject] throw error.')// The error in the first reject layer is also accepted by the next reject layer
}).then((data) = > {
console.log(data,'\n---then2 [resolve] worked.');
},(err) => {
console.log(err,'\n---then2 [reject] worked.');// The result is called here
})
Copy the code
2.2 return to promise
read('./name.txt').then((data) = > {//name. TXT contains the contents of age.txt
return read(data)
}, (err) => {})
.then((data) = > {// The second layer performs the state determination of which promise is completed by the first layer
console.log(data,'\n---then2 [resolve] worked.'); // Execute and print the contents of age.txt
}, (err) => {
console.log(err,'\n---then2 [reject] worked.');
})
Copy the code
- If the error is not caught, it can be caught at any subsequent level and the error will not be passed back
read('./na1me.txt').then((data) = > {Reject = reject; reject = reject
return read(data) // It doesn't matter whether you write it or not, because there is no first then resolve
})
.then((data) = > {
console.log(data,'\n---then2 [resolve] worked.');
}, (err) => {
console.log(err,'\n---then2 [reject] worked.'); No file na1me.txt
})
.then((data) = > {
console.log(data,'\n---then3 [resolve] worked.'); // This is then called because the error was caught at layer 2
}, (err) => {
console.log(err,'\n---then3 [reject] worked.');
// In addition, if the layer 2 then code runs with an error or throw new error, it is executed to catch layer 2 errors instead of layer 1 errors. If the second layer is not passed in reject, the error in the first layer is caught by the third layer here, THEN
})// The code runs with an error if the error is not caught throughout
Copy the code
- Each execution of the promise.then method returns a new ‘promise’, which is why. Then
read('./name.txt').then((data) = > {
return 111
})// Even if 111 is returned, it is wrapped as a promise. The second then is actually the then of the new promise
.then((data) = > {
console.log(data)
})
Copy the code
Mypromise 2.0 source code parsing
No more words, start code, words are in the comments.
const RESOLVED = 'RESOLVED';
const REJECTED = 'REJECTED';
const PENDING = 'PENDING';
class Promise {
constructor(executor) {
this.status = PENDING;
this.value = undefined;
this.reason = undefined;
this.onResolvedCallbacks = [];
this.onRejectedCallbacks = [];
let resolve = (value) = > {
if(this.status === PENDING){
this.value = value;
this.status = RESOLVED;
this.onResolvedCallbacks.forEach(fn= >fn()); }}let reject = (reason) = > {
if(this.status === PENDING){
this.reason = reason;
this.status = REJECTED;
this.onRejectedCallbacks.forEach(fn= >fn()); }}try{
executor(resolve,reject);
}catch(e){
reject(e);
}
}
then(onFulfilled,onRejected){// To implement the chain call, change the entire then function to return a promise, so that the return value has a. Then method
let promise2 = new Promise((resolve, reject) = > {// New Promise is passed to executor and executed immediately
if(this.status ===RESOLVED){
Settimeout = settimeout
// Because the following resolvePromise is passing in promise2 itself
// With setTimeout removed, the executor in the constructor of the new operation of promise2 has not yet been executed
// If the resolvePromise is not initialized, promise2 will not be passed to resolvePromise.
ResolvePromise: resolvePromise: resolvePromise: resolvePromise: resolvePromise: resolvePromise: resolvePromise: resolvePromise: resolvePromise: resolvePromise: resolvePromise: resolvePromise: resolvePromise: resolvePromise: resolvePromise
// Node implements A promise that doesn't use setTimeout, but rather uses A lower-level method, but using setTimeout is one of the recommended methods in the A+ specification, so don't worry.
setTimeout((a)= > {
try {
let x = onFulfilled(this.value); // Then returns the value of the resolve return
// This is a big pity. There is no need to worry about ondepressing, which is also a pity that the user will pass in a resolve error, because the executor that is part of promise2 will be caught by the try/catch of promise2
// Try /catch cannot catch asynchronous errors, so it is necessary to wrap another try/catch layer
resolvePromise(promise2,x,resolve,reject) // There are three cases of x (normal, error, promise), and the function calls resolve or reject based on x
} catch (e) {// Make promise2 fail
reject(e)
}
},0)}// The reason for the change below is the same as here
if(this.status ===REJECTED ){
setTimeout((a)= > {
try {
let x = onRejected(this.reason)
resolvePromise(promise2,x,resolve,reject)
} catch (e) {
reject(e)
}
},0)}if(this.status === PENDING){
this.onResolvedCallbacks.push((a)= >{
setTimeout((a)= > {// It is not necessary to use setTimeout because it is asynchronous. Added to keep code consistent
try {
let x = onFulfilled(this.value);
resolvePromise(promise2,x,resolve,reject)
} catch (e) {
reject(e)
}
},0)});this.onRejectedCallbacks.push((a)= >{
setTimeout((a)= > {
try {
let x = onRejected(this.reason);
resolvePromise(promise2,x,resolve,reject)
} catch (e) {
reject(e)
}
},0)}); }}}})// resolvePromise All promises should stick to bluebird Q ES6 - Promise
const resolvePromise = (promise2, x, resolve, reject) = > {
// 1. Loop references itself and wait for it to finish
// let p = new Promise((resolve,reject) => {
// resolve(1)
// })
// let p2 = p.then(data => {
// return p2// error because the promise p2 returns p2, and resolvePromise,
// // When the return value X of onFulfilled/onRejected is a promise, the returned promise will be implemented, and then the state of P2 will be decided according to the return value of this promise
// // and the value returned here is p2, which is determined by the internal P2, in an infinite loop. So when a user uses a promise this way, an error is reported
// // So when we write resolvePromise ourselves, we need to identify and report errors if the return value is the promise instance itself.
// })
// p2.then(() => {},() => {}) TypeError: Chaining cycle detected for Promise #< promise >
if (promise2 === x) { // I wait for the other person to complete, the condition of the other person to complete is waiting for the other person to call reject
return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))}// Subsequent conditions must be strictly judged to ensure that the code can be used with other libraries
let called;
if ((typeof x === 'object'&& x ! =null) | |typeof x === 'function') { // Check if it is a promise
try {
let then = x.then;
if (typeof then === 'function') { // Check if it is a promise
// A+ = A+ = A+ = A+ = A+ = A+ = A+ = A+ = A+
// ↓ = x.teng (y=>{},e=>{})
then.call(x, y => { // Success or failure depends on the state of the promise
if (called) return;// This prevents the promise library from calling resolve/reject more than once
called = true;// Call true (true); return (true)
resolvePromise(promise2, y, resolve, reject); // The recursive parsing process
}, e => {
if (called) return;
called = true;
reject(e);
});
} else { // 1**resolve(x); }}catch (e) {
if (called) return;
called = true;
reject(e); // The value is incorrect}}else { // 2**
resolve(x);
}
// If the promise condition is not a promise, the else resolve condition is not a promise.
// Then try/catch does not catch the execution of thene. call.
// because let then = x.teng; You might get an error here, so there's a try/catch layer, so you can't write it with other promise conditions
// If you want to get A then, try/catch it. If you want to get A THEN, try/catch it
// If you want to get a parameter assignment, you can get an error. If you want to get a parameter assignment, you can get an error.
//Object.defineProperty(x,'then',{get(){throw new Error()}})
}
module.exports = Promise
Copy the code
Mypromise 2.0 source code uncommented version
Add a version without comments, convenient for everyone to switch to see the structure and logic, too many comments I feel confused
So definitely not for water words 🙂
But I think it’s very troublesome to break them apart, and it’s not convenient for you to copy and run them, so I just write them together.
Without the comments, there isn’t much code, and you can see that there are three main blocks: Executor, then, and resolvePromise
const RESOLVED = 'RESOLVED';
const REJECTED = 'REJECTED';
const PENDING = 'PENDING';
class Promise {
constructor(executor) {
this.status = PENDING;
this.value = undefined;
this.reason = undefined;
this.onResolvedCallbacks = [];
this.onRejectedCallbacks = [];
let resolve = (value) = > {
if(this.status === PENDING){
this.value = value;
this.status = RESOLVED;
this.onResolvedCallbacks.forEach(fn= >fn()); }}let reject = (reason) = > {
if(this.status === PENDING){
this.reason = reason;
this.status = REJECTED;
this.onRejectedCallbacks.forEach(fn= >fn()); }}try{
executor(resolve,reject);
}catch(e){
reject(e);
}
}
then(onFulfilled,onRejected){
let promise2 = new Promise((resolve, reject) = > {
if(this.status ===RESOLVED){
setTimeout((a)= > {
try {
let x = onFulfilled(this.value);
resolvePromise(promise2,x,resolve,reject)
} catch (e) {
reject(e)
}
},0)}if(this.status ===REJECTED ){
setTimeout((a)= > {
try {
let x = onRejected(this.reason)
resolvePromise(promise2,x,resolve,reject)
} catch (e) {
reject(e)
}
},0)}if(this.status === PENDING){
this.onResolvedCallbacks.push((a)= >{
setTimeout((a)= > {
try {
let x = onFulfilled(this.value);
resolvePromise(promise2,x,resolve,reject)
} catch (e) {
reject(e)
}
},0)});this.onRejectedCallbacks.push((a)= >{
setTimeout((a)= > {
try {
let x = onRejected(this.reason);
resolvePromise(promise2,x,resolve,reject)
} catch (e) {
reject(e)
}
},0)}); }}}})const resolvePromise = (promise2, x, resolve, reject) = > {
if (promise2 === x) {
return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))}let called;
if ((typeof x === 'object'&& x ! =null) | |typeof x === 'function') {
try {
let then = x.then;
if (typeof then === 'function') {
then.call(x, y => {
if (called) return;
called = true;
resolvePromise(promise2, y, resolve, reject);
}, e => {
if (called) return;
called = true;
reject(e);
});
} else{ resolve(x); }}catch (e) {
if (called) return;
called = true; reject(e); }}else{ resolve(x); }}module.exports = Promise
Copy the code
conclusion
The code starts with p1, P2, and P3 to refer to three successive promises
let p1 = new Promise((resolve,reject) = > {
resolve('p1')
})
p1
.then((data) = > {// Create a new promise P2
resolve(data + ' + p2')
}, (err) => {
console.log(err);
})
// Actually, here is the. Then method for the second promise p2, which has nothing to do with P1
.then((data) = > {// create a new promise p3
console.log(data + ' + p3');
}, (err) => {
console.log(err);
})
Copy the code
1. Turn the then function itself into a promise, and execute it, thus realizing the chain call that can not stop. Then
So what you do is you write the then function that returns a promise, so then has a state, it has its own then method,p1 calls then produces P2, and passes data to P2 or to THEN, so you can think of then as P2
This is a big pity, which is a big pity, and THEN execute P2 immediately, which is perhaps onFulfilled or onRejected. The implementation result represents the status of P2 which is successful or failed.
According to the state of P2, the second. Then method is executed to realize the chain call, which is the principle of the chain call realized by promise in the synchronous state!!!!
(The asynchronous state is the same, but a little detour, back to summarize the repair)
So p1 is a very straightforward state, so the executor calls resolve p1, reject P1, reject
The user will judge and write p1 in the actuator and decide the state of P1, thus deciding the first one. Then this will be called ondepressing or onRejected
This is a big pity. Then onFulfilled or onRejected, which will be decided according to the state of the first. Then or P2
The status of P2 is decided by the return result X of onFulfilled/onRejected. What is X? This is the second difficult point.
2. Implement resolvePromise
In most cases, whether onFulfilled or onRejected, resolve will be called normally.
However, there are also two other cases. One is the onFulfilled/onRejected code written by the user, which will be called reject
This promise will be implemented. According to the state of this promise, the state of P2 will be decided.
Give it a go (to be done)
There is no operating environment at work, and the test results will be displayed within two or three days
And check the code and run the official test (there should be no problem because it’s the teacher’s code)
additional
Thank you for your patience in reading, there may be some errors that have not been checked, please kindly spray, and we will keep modifying and supplementing later.
Again, this is a note and notes, any mistakes and questions welcome to leave a message, we will reply and correct in time!!
I’ve almost finished writing promises in the midpart, and I’ll write some race all catch methods in the next part.
After writing the Promise, I plan to write a JS engine execution sequence. I will continue my style and write very detailed words (at the same time, I should not have pictures, because I am too lazy). But no more notes! .
We created a small group of front-end rookies and invited people of similar age and level to join.
The only requirement and the original intention of the group is that one article must be posted every week and everyone must be cleared on Tuesday to urge everyone in the group to study.