“This is the 31st day of my participation in the First Challenge 2022. For details: First Challenge 2022”
preface
I’ve always heard that you need to understand some source code to become an advanced front-end programmer, so start with Promise, which the authors tried to get you to understand in as little language as possible
To prepare
Promises/A+原文 : Promises/A+ translation
Install the Promise testing tool
npm i promises-aplus-tests -g
Copy the code
Run the test tool to verify that the Promise complies with the specification
Promises - Aplus -tests [promise name]Copy the code
Complete code is preceded by use cases
const PENDING = 'PENDING';
const FULFILLED = 'FULFILLED';
const REJECTED = 'REJECTED';
class Promise {
constructor(executor) {
if (typeofexecutor ! = ='function') {
return new TypeError(`Promise resolver ${executor} is not a function`);
}
this.state = PENDING;
this.value = null;
this.reason = null;
this.onFulfilledCallbacks = [];
this.onRejectedCallbacks = [];
const resolve = (value) = > {
if (this.state === PENDING) {
this.state = FULFILLED;
this.value = value;
this.onFulfilledCallbacks.forEach((fn) = >fn()); }};const reject = (reason) = > {
if (this.state === PENDING) {
this.state = REJECTED;
this.reason = reason;
this.onRejectedCallbacks.forEach((fn) = >fn()); }};try {
executor(resolve, reject);
} catch (e) {
this.reject(e); }}then(onFulfilled, onRejected) {
if (typeofonFulfilled ! = ='function') {
onFulfilled = (value) = > value;
}
if (typeofonRejected ! = ='function') {
onRejected = (err) = > {
throw err;
};
}
let promise2 = new Promise((resolve, reject) = > {
if (this.state === FULFILLED) {
setTimeout(() = > {
try {
const x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch(e) { reject(e); }},0);
}
if (this.state === REJECTED) {
setTimeout(() = > {
try {
const x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch(e) { reject(e); }},0);
}
if (this.state === PENDING) {
this.onFulfilledCallbacks.push(() = > {
setTimeout(() = > {
try {
const x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch(e) { reject(e); }}); });this.onRejectedCallbacks.push(() = > {
setTimeout(() = > {
try {
const x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch(e) { reject(e); }}); }); }});returnpromise2; }}const resolvePromise = (promise2, x, resolve, reject) = > {
if (promise2 === x)
return reject(
new TypeError('Chaining cycle detected for promise #<Promise>'));if ((typeof x === 'object'&& x ! = =null) | |typeof x === 'function') {
let called;
try {
const then = x.then;
if (typeofthen ! = ='function') resolve(x);
else {
then.call(
x,
(value) = > {
if (called) return;
called = true;
resolvePromise(promise2, value, resolve, reject);
},
(reason) = > {
if (called) return;
called = true; reject(reason); }); }}catch (err) {
if (called) return;
called = true; reject(err); }}else{ resolve(x); }};Promise.defer = Promise.deferred = function () {
let dfd = {};
dfd.promise = new Promise((resolve, reject) = > {
dfd.resolve = resolve;
dfd.reject = reject;
});
return dfd;
};
module.exports = Promise;
Copy the code
The complete code is 127 lines, adding the Promise test code for a total of 136 lines.
How to test
In VS Code file new promise. Jspromise. Jspromise. Js file
/ / promise code
class Promise {
constructor() {}
then() {}
catch() {}}// Test the code
Promise.defer = Promise.deferred = function () {
let dfd = {};
dfd.promise = new Promise((resolve, reject) = > {
dfd.resolve = resolve;
dfd.reject = reject;
});
return dfd;
};
module.exports = Promise;
Copy the code
In promise. Jspromise. Jspromise. Js file directory open a terminal, enter the following command to test promise is in accordance with the specification
promises-aplus-tests promise.js
Copy the code
For the rest of this tutorial, I'll stop explaining the test code and just talk about the Promise code
Keep up with the ideas, the core of this article begins
The statement Promise class
Common Promise methods include then and catch, which declare a Promise class using ES6 syntax
That’s easy to understand, right?
class Promise {
constructor() {}
then() {}
catch() {}}Copy the code
constructor
A normal use of a Promise would look like this: New Promise(parameter is a function)
let p1 = new Promise((resolve) = > {
setTimeout(() = > {
resolve(1);
}, 1000);
});
Copy the code
So is it understandable that the Constructor of the Promise class uses the executor parameter to receive this parameter?
class Promise {
constructor(executor) {}
then() {}
catch() {}}Copy the code
Continue to:
Executor is a parameter entered by the user. We can’t trust the user to use a function as a new Promise, right?
So you need to determine whether executor is a function.
Makes sense, right?
class Promise {
constructor(executor) {
if (typeofexecutor ! = ='function') {
return new TypeError(`Promise resolver ${executor} is not a function`); }}then() {}
catch() {}}Copy the code
executor
The constructor section already knows that executor is a function. Executor functions take arguments.
The following Promise uses the code
let p1 = new Promise((resolve, reject) = > {
setTimeout(() = > {
Math.random() > 0.5 ? resolve('right') : reject('wrong');
}, 1000);
});
p1.then((value) = > {
console.log(value);
});
Copy the code
Promises/A+ : Resolve is A set of actions that will succeed, reject is A set of actions that will fail, and cause success. We have to have all of these promises, all of them
Resolve and reject
class Promise {
constructor(executor) {
if (typeofexecutor ! = ='function') {
return new TypeError(`Promise resolver ${executor} is not a function`);
}
// The value passed to the resolution callback when a promise is resolved
this.value = null;
// The value passed to the resolution callback when a promise is rejected
this.reason = null;
// Successful sequence of operations
const resolve = () = > {};
// Some operations failed
const reject = () = > {};
executor(resolve, reject);
}
then() {}
catch() {}}Copy the code
state
Promises/A + translation
Promises/A+ : Promises have three states. Let’s add these three states to our code, and that makes sense
Constants are named in uppercase, which is understandable
PENDING, depressing and REJECTED
const PENDING = 'PENDING';
const FULFILLED = 'FULFILLED';
const REJECTED = 'REJECTED';
class Promise {
constructor(executor) {
if (typeofexecutor ! = ='function') {
return new TypeError(`Promise resolver ${executor} is not a function`); }}then() {}
catch() {}}Copy the code
The Pending state can only be Fulfilled or Rejected and cannot be changed. This requires a variable to record the Promise state value, and the initial value of the state value is Pending
this.state
const PENDING = 'PENDING';
const FULFILLED = 'FULFILLED';
const REJECTED = 'REJECTED';
class Promise {
constructor(executor) {
if (typeofexecutor ! = ='function') {
return new TypeError(`Promise resolver ${executor} is not a function`);
}
this.state = PENDING;
this.value = null;
this.reason = null;
const resolve = () = > {};
const reject = () = > {};
executor(resolve, reject);
}
then() {}
catch() {}}Copy the code
Pending can only be Fulfilled Fulfilled or Rejected means resolve and reject can only be executed Pending.
So you need to add judgments to the resolve and reject functions
const PENDING = 'PENDING';
const FULFILLED = 'FULFILLED';
const REJECTED = 'REJECTED';
class Promise {
constructor(executor) {
if (typeofexecutor ! = ='function') {
return new TypeError(`Promise resolver ${executor} is not a function`);
}
this.state = PENDING;
this.value = null;
this.reason = null;
const resolve = () = > {
// The execution can be performed only when the execution is completed. The modification state is depressing
if (this.state === PENDING) {
this.state = FULFILLED; }};const reject = () = > {
// The value can be executed only when the value is PENDING. The value can be modified to REJECTED after the execution
if (this.state === PENDING) {
this.state = REJECTED; }}; executor(resolve, reject); }then() {}
catch() {}}Copy the code
Does the success/exception function need to have success/exception data? Does the value of the callback need to be stored in the class and returned in the then method?
add
The value and reason
const PENDING = 'PENDING';
const FULFILLED = 'FULFILLED';
const REJECTED = 'REJECTED';
class Promise {
constructor(executor) {
if (typeofexecutor ! = ='function') {
return new TypeError(`Promise resolver ${executor} is not a function`);
}
this.state = PENDING;
this.value = null;
this.reason = null;
const resolve = (value) = > {
if (this.state === PENDING) {
this.state = FULFILLED;
// The value passed to the resolution callback at resolution time
this.value = value; }};const reject = (reason) = > {
if (this.state === PENDING) {
this.state = REJECTED;
// The value passed to the resolution callback when rejected
this.reason = reason; }}; executor(resolve, reject); }then() {}
catch() {}}Copy the code
Has this been completed? No, one more step.
Executor is a function, as described above, but it is possible for the function to execute an error. Try/catch solution
const PENDING = 'PENDING';
const FULFILLED = 'FULFILLED';
const REJECTED = 'REJECTED';
class Promise {
constructor(executor) {
if (typeofexecutor ! = ='function') {
return new TypeError(`Promise resolver ${executor} is not a function`);
}
this.state = PENDING;
this.value = null;
this.reason = null;
const resolve = (value) = > {
if (this.state === PENDING) {
this.state = FULFILLED;
// The value passed to the resolution callback at resolution time
this.value = value; }};const reject = (reason) = > {
if (this.state === PENDING) {
this.state = REJECTED;
// The value passed to the resolution callback when rejected
this.reason = reason; }};try {
// If the function runs smoothly, execute the function
executor(resolve, reject);
} catch (error) {
// The function throws an exception, which is returned by rejectreject(error); }}then() {}
catch() {}}Copy the code
Then
Write the then function alone and ignore the constructor code for now. Too much code can be confusing. What do readers think?
then() {}
Copy the code
Promises/A + translation
According to the specification, then should have two parameters, and these two parameters should be functions, if not functions, then should be ignored
So the then code looks like this; It’s not hard to understand
then(onFulfilled, onRejected){
if (typeofonFulfilled ! = ='function') {
onFulfilled = (value) = > value;
}
if (typeofonRejected ! = ='function') {
onRejected = (err) = > {
throwerr; }; }}Copy the code
OnFulfilled and onRejected
Then look at the specification description
OnFulfilled and onRejected cannot be called more than once.
then(onFulfilled, onRejected) {
if (typeofonFulfilled ! = ='function') {
onFulfilled = (value) = > value;
}
if (typeofonRejected ! = ='function') {
onRejected = (err) = > {
throw err;
};
}
// Execute only once
if (this.state === Promise.FULFILLED) {
onFulfilled(this.value);
}
if (this.state === Promise.REJECTED) {
onRejected(this.reason); }}Copy the code
Resolve Supports asynchrony
Is that all? No, no, no.
If resolve is implemented in an asynchronous function in the Promise, console is not currently implemented in the Promise code I’m writing.
let p2 = new Promise((resolve) = > {
setTimeout(() = > {
resolve(2);
}, 1000);
});
p2.then((value) = > {
console.log(value);
});
Copy the code
The reason is that when the. Then function is executed, the Promise state is Pending. Currently, I only write the state as FULFILLED and REJECTED in the Promise
The.then function also handles PENDING state bits. The incoming arguments to the.THEN function are placed in an array before the asynchronous execution of resolve
Any other questions?
some
Then the asynchronous
console.log(1);
let p2 = new Promise((resolve) = > {
resolve(4);
console.log(2);
});
p2.then((value) = > {
console.log(value);
});
console.log(3);
// 1,2,3,4
Copy the code
Normally returns 1, 2, 3, 4. Now I’m writing a promise that returns 1, 2, 4, 3
What’s the reason? The solution is to add setTimeout to the.then function to simulate asynchrony
then(onFulfilled, onRejected) {
if (typeofonFulfilled ! = ='function') {
onFulfilled = (value) = > value;
}
if (typeofonRejected ! = ='function') {
onRejected = (err) = > {
throw err;
};
}
if (this.state === FULFILLED) {
setTimeout(() = > {
onFulfilled(this.value);
});
}
if (this.state === REJECTED) {
setTimeout(() = > {
onRejected(this.reason);
});
}
if (this.state === PENDING) {
this.onFulfilledCallbacks.push(() = > {
setTimeout(() = > {
onFulfilled(this.value);
});
});
this.onRejectedCallbacks.push(() = > {
setTimeout(() = > {
onRejected(this.reason);
});
});
}
let promise2 = new Promise(() = > {});
returnpromise2; }}Copy the code
The array of promises needs to be declared in constructor and executed in resolve, so the code Promise up to this point looks like this:
const PENDING = 'PENDING';
const FULFILLED = 'FULFILLED';
const REJECTED = 'REJECTED';
class Promise {
constructor(executor) {
if (typeofexecutor ! = ='function') {
return new TypeError(`Promise resolver ${executor} is not a function`);
}
this.state = PENDING;
this.value = null;
this.reason = null;
this.onFulfilledCallbacks = [];
this.onRejectedCallbacks = [];
const resolve = (value) = > {
if (this.state === PENDING) {
this.state = FULFILLED;
this.value = value;
this.onFulfilledCallbacks.forEach((fn) = >fn()); }};const reject = (reason) = > {
if (this.state === PENDING) {
this.state = REJECTED;
this.reason = reason;
this.onRejectedCallbacks.forEach((fn) = >fn()); }};try {
executor(resolve, reject);
} catch (e) {
this.reject(e); }}then(onFulfilled, onRejected) {
if (typeofonFulfilled ! = ='function') {
onFulfilled = (value) = > value;
}
if (typeofonRejected ! = ='function') {
onRejected = (err) = > {
throw err;
};
}
if (this.state === FULFILLED) {
setTimeout(() = > {
onFulfilled(this.value);
});
}
if (this.state === REJECTED) {
setTimeout(() = > {
onRejected(this.reason);
});
}
if (this.state === PENDING) {
this.onFulfilledCallbacks.push(() = > {
setTimeout(() = > {
onFulfilled(this.value);
});
});
this.onRejectedCallbacks.push(() = > {
setTimeout(() = > {
onRejected(this.reason); }); }); }}}Copy the code
So I’m going to write this, and I’m going to test it and it says 806 exceptions; That’s 66 test cases passed. Continue to work hard
How does then return a new Promise
Then look at the specification description:
The then method must return a Promise object
then(onFulfilled, onRejected) {
if (typeofonFulfilled ! = ='function') {
onFulfilled = (value) = > value;
}
if (typeofonRejected ! = ='function') {
onRejected = (err) = > {
throw err;
};
}
if (this.state === FULFILLED) {
setTimeout(() = > {
onFulfilled(this.value);
});
}
if (this.state === REJECTED) {
setTimeout(() = > {
onRejected(this.reason);
});
}
if (this.state === PENDING) {
this.onFulfilledCallbacks.push(() = > {
setTimeout(() = > {
onFulfilled(this.value);
});
});
this.onRejectedCallbacks.push(() = > {
setTimeout(() = > {
onRejected(this.reason);
});
});
}
// Return a new Promise object
let promise2 = new Promise(() = > {});
return promise2;
}
Copy the code
Understandable? Each time.then returns a new promise object, the.then method can be called forever
Why can’t we just return this? JQuery just returns this for chain calls.
Because a promise has three states and the states are irreversible, a new Promise object must be returned
Then returns the Promise resolution process
Then look at the specification description:
- if
onFulfilled
oronRejected
Return a valuex
, run the followingPromise resolution process:[[Resolve]](promise2, x)
- if
onFulfilled
oronRejected
Throw an exceptione
,promise2
Execution must be rejected and a rejection must be returnede
- if
onFulfilled
Is not a function andpromise1
Successful execution,promise2
Must execute successfully and return the same value - if
onRejected
Is not a function andpromise1
Refuse to enforce,promise2
Execution must be rejected and the same data returned
The first sentence
Make sure that onFulfilled or onRejected returns a value x to perform Promise2
Modify the then code as follows:
then(onFulfilled, onRejected) {
if (typeofonFulfilled ! = ='function') {
onFulfilled = (value) = > value;
}
if (typeofonRejected ! = ='function') {
onRejected = (err) = > {
throw err;
};
}
let promise2 = new Promise((resolve, reject) = > {
if (this.state === FULFILLED) {
setTimeout(() = > {
const x = onFulfilled(this.value);
});
}
if (this.state === REJECTED) {
setTimeout(() = > {
const x = onRejected(this.reason);
});
}
if (this.state === PENDING) {
this.onFulfilledCallbacks.push(() = > {
setTimeout(() = > {
const x = onFulfilled(this.value);
});
});
this.onRejectedCallbacks.push(() = > {
setTimeout(() = > {
const x = onRejected(this.reason); }); }); }});returnpromise2; }}Copy the code
The second sentence
If ondepressing or onRejected throws an exception E, promise2 must reject the implementation and return the rejection cause E
Catch the exception that ondepressing or onRejected throws with try/catch
then(onFulfilled, onRejected) {
if (typeofonFulfilled ! = ='function') {
onFulfilled = (value) = > value;
}
if (typeofonRejected ! = ='function') {
onRejected = (err) = > {
throw err;
};
}
let promise2 = new Promise((resolve, reject) = > {
if (this.state === FULFILLED) {
setTimeout(() = > {
try {
const x = onFulfilled(this.value);
} catch(error) { reject(error); }}); }if (this.state === REJECTED) {
setTimeout(() = > {
try {
const x = onRejected(this.reason);
} catch(error) { reject(error); }}); }if (this.state === PENDING) {
this.onFulfilledCallbacks.push(() = > {
setTimeout(() = > {
try {
const x = onFulfilled(this.value);
} catch(error) { reject(error); }}); });this.onRejectedCallbacks.push(() = > {
setTimeout(() = > {
try {
const x = onRejected(this.reason);
} catch(error) { reject(error); }}); }); }});return promise2;
}
Copy the code
The third sentence
If ondepressing is not a function and promise1 executes successfully, promise2 must execute successfully and return the same value
Resolve (x) : resolve(x)
then(onFulfilled, onRejected) {
if (typeofonFulfilled ! = ='function') {
onFulfilled = (value) = > value;
}
if (typeofonRejected ! = ='function') {
onRejected = (err) = > {
throw err;
};
}
let promise2 = new Promise((resolve, reject) = > {
if (this.state === FULFILLED) {
setTimeout(() = > {
try {
const x = onFulfilled(this.value);
resolve(x);
} catch(error) { reject(error); }}); }if (this.state === REJECTED) {
setTimeout(() = > {
try {
const x = onRejected(this.reason);
// 这里直接 resolve 并并且返回 x
resolve(x);
} catch(error) { reject(error); }}); }if (this.state === PENDING) {
this.onFulfilledCallbacks.push(() = > {
setTimeout(() = > {
try {
const x = onFulfilled(this.value);
resolve(x);
} catch(error) { reject(error); }}); });this.onRejectedCallbacks.push(() = > {
setTimeout(() = > {
try {
const x = onRejected(this.reason);
resolve(x);
} catch(error) { reject(error); }}); }); }});return promise2;
}
Copy the code
If x is a primitive datatype, let’s think about it, what if x is a reference datatype, a function, or even a Promise?
So we need to determine the data type of x
Now that it’s self-contained, let’s just add a method that executes these x values
The method name is: resolvePromise
then(onFulfilled, onRejected) {
if (typeofonFulfilled ! = ='function') {
onFulfilled = (value) = > value;
}
if (typeofonRejected ! = ='function') {
onRejected = (err) = > {
throw err;
};
}
let promise2 = new Promise((resolve, reject) = > {
if (this.state === FULFILLED) {
setTimeout(() = > {
try {
const x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch(error) { reject(error); }}); }if (this.state === REJECTED) {
setTimeout(() = > {
try {
const x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch(error) { reject(error); }}); }if (this.state === PENDING) {
this.onFulfilledCallbacks.push(() = > {
setTimeout(() = > {
try {
const x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch(error) { reject(error); }}); });this.onRejectedCallbacks.push(() = > {
setTimeout(() = > {
try {
const x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch(error) { reject(error); }}); }); }});return promise2;
}
function resolvePromise(promise2, x, resolve, reject){}Copy the code
resolvePromise
Let’s start writing the resolvePromise function
X is not promise itself
Promise can’t be called in a loop,
function resolvePromise(promise2, x, resolve, reject) {
if (promise2 === x) {
return reject(
new TypeError('Chaining cycle detected for promise #<Promise>')); }}Copy the code
Determine if x is a reference data type
If it is not a reference data type, simply execute resolve(x)
function resolvePromise(promise2, x, resolve, reject) {
if (promise2 === x) {
return reject(
new TypeError('Chaining cycle detected for promise #<Promise>')); }if ((typeof x === 'object'&& x ! = =null) | |typeof x === 'function') {}else{ resolve(x); }}Copy the code
If it is a reference data type, determine if there is then in the data. If there is then that is not a function, execute resolve(x).
Here’s an explanation: if then is a function, x may be a Promise object, requiring special treatment, or if it is not a function, it may be treated as a general object
function resolvePromise(promise2, x, resolve, reject) {
if (promise2 === x) {
return reject(
new TypeError('Chaining cycle detected for promise #<Promise>')); }if ((typeof x === 'object'&& x ! = =null) | |typeof x === 'function') {
const then = x.then;
if (typeofthen ! = ='function') {
resolve(x);
} else{}}else{ resolve(x); }}Copy the code
Of course, exceptions can occur in any code, so try/catch is needed to catch some possible exceptions
function resolvePromise(promise2, x, resolve, reject) {
if (promise2 === x) {
return reject(
new TypeError('Chaining cycle detected for promise #<Promise>')); }if ((typeof x === 'object'&& x ! = =null) | |typeof x === 'function') {
try {
const then = x.then;
if (typeofthen ! = ='function') {
resolve(x);
} else{}}catch(err) { reject(err); }}else{ resolve(x); }}Copy the code
If then is a function
Then is a function that executes then. Because the then is fetched from x, we call the call method to redirect the this of the then function to x
function resolvePromise(promise2, x, resolve, reject) {
if (promise2 === x) {
return reject(
new TypeError('Chaining cycle detected for promise #<Promise>')); }if ((typeof x === 'object'&& x ! = =null) | |typeof x === 'function') {
try {
const then = x.then;
if (typeofthen ! = ='function') {
resolve(x);
} else {
then.call(
x,
(value) = > {
resolvePromise(promise2, value, resolve, reject);
},
(reason) = >{ reject(reason); }); }}catch(err) { reject(err); }}else{ resolve(x); }}Copy the code
The above code does most of what promises do, but there is one more thing that the specification says: If both resolvePromise and rejectPromise are called, or if they are called more than once by the same parameter, the first call is preferred and the remaining calls are ignored
Calledcalledcalled, called true returns,
const resolvePromise = (promise2, x, resolve, reject) = > {
if (promise2 === x)
return reject(
new TypeError('Chaining cycle detected for promise #<Promise>'));if ((typeof x === 'object'&& x ! = =null) | |typeof x === 'function') {
let called;
try {
const then = x.then;
if (typeofthen ! = ='function') resolve(x);
else {
then.call(
x,
(value) = > {
if (called) return;
called = true;
resolvePromise(promise2, value, resolve, reject);
},
(reason) = > {
if (called) return;
called = true; reject(reason); }); }}catch (err) {
if (called) return;
called = true; reject(err); }}else{ resolve(x); }};Copy the code
conclusion
Although I want to describe or explain this Promise in as few words as possible. But it still ended up using nearly 4,000 words to describe what it was. The author’s current level is limited, so he has to make the Promise as clear as possible. If you have any comments and suggestions, please leave a comment in the comment section.