preface
The Promise implementation solves the problem of callback hell by writing asynchronous code synchronously. Essentially, promises are an implementation of the observer pattern that optimizes the original programming pattern through task registration and state listening, increasing the readability and maintainability of code.
Writing a Promise by hand can deepen our understanding of promises. The Promise core consists of three properties, a constructor, two instance methods, and four static methods.
Class initialization
Start by defining two properties to hold the state and result of the Promise.
class Promise {
constructor(executor) {
this.PromiseState = 'pending' // Store the promise state
this.PromiseResult = null // Store promise results}}Copy the code
Executor function
When instantiating a Promise, the user needs to pass in an executor function, which receives two internal functions to change the state of the Promise, so in this step we need:
- Define internal functions: resolve, reject Modifies the promise state and saves the value.
- Call the executor function: Executor executes synchronously. If an internal exception is thrown, the promise state is Rejected.
class Promise {
constructor(executor) {
this.PromiseState = 'pending'; // Store the promise state
this.PromiseResult = null; // Store promise results
// Change the promise state to succeed
const resolve = (data) = > {
// If the state has changed, it will not change
if (this.PromiseState ! = ='pending') return;
// If the state does not change, change the state and save the value
this.PromiseState = 'fulfilled';
this.PromiseResult = data;
}
// Change the promise state to failed
const reject = (data) = > {
// If the state has changed, it will not change
if (this.PromiseState ! = ='pending') return;
// If the state does not change, change the state and save the value
this.PromiseState = 'rejected';
this.PromiseResult = data;
}
// The executor function executes synchronously
// If an exception is thrown during execution, the promise state is failed
try {
executor(resolve, reject);
} catch(error) { reject(error); }}}Copy the code
Then method
Next we need to define the then method, through which the user registers a callback function that will be executed when the Promise state changes.
- Then receives two callback functions as arguments. We need to check onResolved and onRejected.
- The then method returns a Promise and adjusts the promise state based on the onResolved, onRejected implementation.
- The onResolved and onRejected functions in the then method are executed asynchronously.
- If the state of the Promise has not changed by then execution, the callback function is saved and waits to execute, and a third property, callbacks, is added to hold the callback method registered in then.
class Promise {
constructor(executor) {
this.PromiseState = 'pending'; // Store the promise state
this.PromiseResult = null; // Store promise results
this.callbacks = []; // Save the callback method added in then
// Change the promise state to succeed
const resolve = (data) = > {
// If the state has changed, it will not change
if (this.PromiseState ! = ='pending') return;
// If the state does not change, change the state and save the value
this.PromiseState = 'fulfilled';
this.PromiseResult = data;
// Execute the callback saved in then
this.callbacks.forEach((item) = > {
item.onResolved();
});
};
// Change the promise state to failed
const reject = (data) = > {
// If the state has changed, it will not change
if (this.PromiseState ! = ='pending') return;
// If the state does not change, change the state and save the value
this.PromiseState = 'rejected';
this.PromiseResult = data;
// Execute the callback saved in then
this.callbacks.forEach((item) = > {
item.onRejected();
});
};
// The executor function executes synchronously
// If an exception is thrown during execution, the promise state is failed
try {
executor(resolve, reject);
} catch(error) { reject(error); }}/ / then method
then(onResolved, onRejected) {
// check onResolved, onRejected
if (typeofonResolved ! = ='function') {
onResolved = value= > value
}
if (typeofonRejected ! = ='function') {
onRejected = error= > {
throw error
}
}
// Then returns a Promise
return new Promise((resolve, reject) = > {
// Then returns the Promise state based on onResolved and onRejected
const callback = (func) = > {
// Catch an exception thrown when the function executes (trycatch cannot be caught at executor due to asynchronous execution)
try {
const result = func(this.PromiseResult);
if (result instanceof Promise) {
result.then(
(value) = > {
resolve(value);
},
(error) = >{ reject(error); }); }else{ resolve(result); }}catch(error) { reject(error); }};// If the PROMISE state has changed, the callback is executed asynchronously
// In real life, callbacks are added to the microtask queue, which is simulated by macro tasks
if (this.PromiseState === 'fulfilled') {
setTimeout(() = > {
callback(onResolved);
});
}
if (this.PromiseState === 'rejected') {
setTimeout(() = > {
callback(onRejected);
});
}
// If the promise state has not changed, save it first
These methods are executed when the resolve or reject methods are executed in the executor function
if (this.PromiseState === 'pending') {
this.callbacks.push({
onResolved: () = > {
setTimeout(() = > {
callback(onResolved);
});
},
onRejected: () = > {
setTimeout(() = >{ callback(onRejected); }); }}); }}); }}Copy the code
Catch method
The catch method is the syntactic sugar for the then method, so let’s define it.
// The catch method is defined inside the Promise class.
catch(onRejected) {
return this.then(undefined, onRejected);
}
Copy the code
Resolve method
Next we define several static methods, starting with resolve, which wraps the data as a Promise and returns it if the data passed in is a Promise, otherwise returning a Promise that takes the input as the success state of the result
/ / resolve method
static resolve(data) {
// If a Promise is passed in, it is returned directly
if (data instanceof Promise) {
return data;
} else {
return new Promise((resolve) = >{ resolve(data); }); }}Copy the code
Reject method
Reject, which returns a failed Promise regardless of whether the input is a Promise or not, and the value is the incoming data
/ / reject method
static reject(data) {
return new Promise((_, reject) = > {
reject(data);
});
}
Copy the code
All methods
The all method is used to determine whether multiple promises are successful. If multiple promises are successful, an array of returned values for all incoming promises is returned. If one promise fails, the return value of that failed promise is returned
/ / all methods
static all(promises) {
let count = 0;
let arr = [];
return new Promise((resolve, reject) = > {
for (let i = 0; i < promises.length; i++) {
promises[i].then(
(value) = > {
count++;
arr[i] = value;
if(count === promises.length) { resolve(arr); }},(error) = >{ reject(error); }); }}); }Copy the code
Race method
Finally, race, which is used for multiple promise competing implementations, returns the return value of that successful promise if one of them succeeds; If one of the promises fails, the return value of that failed promise is returned
/ / race method
static race(promises) {
return new Promise((resolve, reject) = > {
for (let i = 0; i < promises.length; i++) {
promises[i].then(
(value) = > {
resolve(value);
},
(error) = >{ reject(error); }); }}); }Copy the code