This article realizes a Promise object to get an in-depth understanding of the operation principle of Promise. Before we start writing code to implement a Promise instance, let’s think about what JS does when we create a Promise instance. Here’s an example:
let p = new Promise((resolve,reject) =>{
let a = 3 + 1;
a == 4 ? resolve(a) : reject(a);
})
Copy the code
We create a Promise instance with an execution function that must have resolve and reject arguments that hold the successful value or failure reason and modification status. As soon as the object is created, the Promise constructor runs the function passed in. This will be a pity, which is the big Promise. This will be a pity, which is the big Promise. This will be a pity, which is the big Promise. Ok, now we can start writing an object called MyPromise:
const PENDING = 'pending'; // this is a big pity; // const REJECTED = 'REJECTED '; Constructor (executor){constructor(executor){try {executor(this.resolve, this.reject); } catch(e) { This.reject(e) } } status = PENDING; value = undefined; reason = undefined; resolve => value => { if(this.status ! == PENDING) return; this.status = FULFILLED; this.value = value} reject => reason => { if(this.status ! == PENDING) return; this.status = REJECTED; this.reason = reason} }Copy the code
Now you’ve defined a set of member variables and constants, and you’ve written the constructors. We define resolve and reject, which change the value of status and decide to exit if status is not pending. The successful value or the reason for the failure is finally saved.
With the constructor and set of member variables in place, we now look at the key method in Promise: then. Then contains two callback functions that are called based on the state and hold the value that comes from the Promise. Such as:
let p = new Promise((resolve,reject) =>{ let a = 3 + 1; a == 4 ? resolve(a) : reject(a); }) p.chen (res => console.log(res)) // The result is 4Copy the code
Then ends with a new Promise instance. Now let’s think about how to write it. Don’t think about the content, think about the general form. There must be a successCallback and a failCallback with two parameters, which we’ll call successCallback and failCallback, and then the result must be a Promise instance. Ok, now let’s write it in code:
then (successCallback, failCallback) { ... // let promise = new MyPromise((resolve, reject) => {... }) return promise }Copy the code
It’s something like that.
Then we need to think about what the then method does in the first place. If a non-callback function is used, the value passed in from the previous Promise object will be placed directly in the new Promise:
Then (value => return promise.resolve (value));Copy the code
So then determines whether the argument passed to it is a callback function and processes it accordingly. Write it in code:
then (successCallback, failCallback) { successCallback = successCallback ? successCallback : value => value; failCallback = failCallback ? failCallback: reason => { throw reason }; . }Copy the code
If not, a function that generates the value passed in for success or a function that throws the cause of failure. A new Promise instance is then generated, and the executing function decides which function to call based on the state of the previous Promise. So let’s write the success and failure here.
then (successCallback, failCallback) { ... let promise = new MyPromise((resolve, reject) => { if (this.status === FULFILLED) { try { let x = successCallback(this.value); }catch (e) { reject(e); } }else if (this.status === REJECTED) { try { let x = failCallback(this.reason); }catch (e) { reject(e); }}}); return promise;Copy the code
Remember that the callback function in then is to be placed on the microtask queue, so we need to call queueMicrotask, so the code should read:
. let promise = new MyPromise((resolve, This is a big pity. () => {try {let x = successCallback(this.value); }catch (e) { reject(e); } }) }else if (this.status === REJECTED) { queueMicrotask(() => { try { let x = failCallback(this.reason); }catch (e) { reject(e); }})}... return promiseCopy the code
Ok, now the then method is working, but we need to resolve or reject to confirm the state of the Promise instance. We could pass the value of the callback directly to resolve or reject, but we don’t know what we’re passing. If we pass the Promise itself, we’ll have an infinite loop, which means we need to determine the value before we can resolve or reject. We can write a private method, we can write a private method in JS, we can write a function outside of class (in typescript, we can use private methods in object-oriented languages).
function resolvePromise (promise, x, resolve, reject) { if (promise === x) { return reject(new TypeError('Chaining cycle detected for promise #<Promise>')) } if (x Instanceof MyPromise) {// createPromise (value => resolve(value), reason => reject(reason)); x.then(resolve, reject); } else {resolve(x); }}Copy the code
Determine if the value passed is the same as the Promise instance you created, and if so, an error is reported. If a value is passed in, call resolve of the Promise instance directly. In the case of a Promise instance, the then of that instance is called directly, passing resolve and reject as callbacks. Note that resolve and Reject are promise instances, not promise instances emanating from the callback function. Before we put this private method into the THEN method, let’s consider the final state, pending.
If the state of a Promise is pending, the code is not finished yet, so it must be run. However, since the state is not determined, the callback function of then must be stored first, and the corresponding callback function will be proposed and executed after the state is determined. So we need to refactor the Promise by adding two arrays to hold successful and failed callbacks, and then resolve and Reject to determine if the array has a callback, and if so, call it. The corresponding code is as follows:
SuccessCallback = []; failCallback = []; resolve = value => { if (this.status ! == PENDING) return; this.status = FULFILLED; this.value = value; while(this.successCallback.length) this.successCallback.shift()() } reject = reason => { if (this.status ! == PENDING) return; this.status = REJECTED; this.reason = reason; while(this.failCallback.length) this.failCallback.shift()() }Copy the code
Add two new arrays to store successful and failed callbacks, and resolve and Reject to call first-in, first-out.
In the THEN method, add the code to handle pending, and call the array that stores the function to save the callback function:
this.successCallback.push(() => { queueMicrotask(() => { try { let x = successCallback(this.value); resolvePromise(promise, x, resolve, reject) }catch (e) { reject(e); }})}); this.failCallback.push(() => { queueMicrotask(() => { try { let x = failCallback(this.reason); resolvePromise(promise, x, resolve, reject) }catch (e) { reject(e); }})});Copy the code
Now we’re done with the then method. The complete code is as follows:
then (successCallback, failCallback) { successCallback = successCallback ? successCallback : value => value; failCallback = failCallback ? failCallback: reason => { throw reason }; let promise = new MyPromise((resolve, reject) => { if (this.status === FULFILLED) { queueMicrotask(() => { try { let x = successCallback(this.value); resolvePromise(promise, x, resolve, reject) }catch (e) { reject(e); } }) }else if (this.status === REJECTED) { queueMicrotask(() => { try { let x = failCallback(this.reason); resolvePromise(promise, x, resolve, reject) }catch (e) { reject(e); } }) } else { this.successCallback.push(() => { queueMicrotask(() => { try { let x = successCallback(this.value); resolvePromise(promise, x, resolve, reject) }catch (e) { reject(e); }})}); this.failCallback.push(() => { queueMicrotask(() => { try { let x = failCallback(this.reason); resolvePromise(promise, x, resolve, reject) }catch (e) { reject(e); }})}); }}); return promise; }Copy the code
Basically, at this point, the Promise implementation is mostly done, and the rest of the method will be implemented later.
Let’s start with catch. Catch is responsible for calling the failure callback, passing in the previously passed cause of failure as an argument. Therefore, the code could be written like this:
catch (failCallback) {
return this.then(undefined, failCallback) }
Copy the code
Then is called, but there is no successful callback.
Resolve is a static method that returns an instance of a resolve Promise, that is, the state of the Promise is fulfilled.
static resolve (value) {
if (value instanceof MyPromise) return value;
return new MyPromise(resolve => resolve(value)); }}
Copy the code
Determine the execution process based on the parameters passed in. If it is a Promise instance, return it directly; otherwise, put the parameters into a new Promise instance.
The finally method calls the callback function passed in and returns a new Promise instance regardless of success or failure.
finally (callback) {
return this.then(value => {
return MyPromise.resolve(callback()).then(() => value);
}, reason => {
return MyPromise.resolve(callback()).then(() => { throw reason })
}) }
Copy the code
The all method can take an array of arguments and pass out a new Promise instance with an array of results as the success value of the Promise. If the element in the array is a value, return it as the result. If it is a Promise, the result of its execution function will be processed accordingly. If it succeeds, the result array will be placed; otherwise, its failed function will be called directly, so the new Promise’s resolve will not be called.
static all (array) { let result = []; let index = 0; return new MyPromise((resolve, reject) => { function addData (key, value) { result[key] = value; index++; if (index === array.length) { resolve(result); } } for (let i = 0; i < array.length; i++) { let current = array[i]; If (current instanceof MyPromise) {// Promise object. Then (value => addData(I, value), Reason => reject(reason))}else {// Common value addData(I, array[I]); }}})}Copy the code
Basically, the Promise method has been implemented, and the rest I’ve attached is the complete code.
By implementing Promise, we learned how it worked, and we unknowingly learned functional programming. Functional programming means programming in a purely functional way. Simply put, a pure function is a function whose output remains the same regardless of how many times it is executed. The implementation of promises also embodies this idea. When a new Promise instance is created, the constructor executes the function passed in, determining the state of the Promise and determining the successful and failed values. After a build, the instance’s member variables are determined, and no method is called to change the variables inside.
What’s the good of that? The advantage is that the results of calling an instance’s methods are predictable and easy to test by maintaining immutability.
Finally, attach the complete code:
Codepen. IO/dominguitol…