[JavaScript] How to write a Promise by hand

foreword

Main content of this article: Learn more about the operating mechanism of Promise by writing a myPromise object

Handwriting Promise should be the best way to understand the logic of Promise, so although there are already countless articles on handwriting Promise on Nuggets, I still intend to deepen my understanding by handwriting Promise through a method similar to taking notes

define myPromise

Let's first look at how the original Promise is defined

const promise = new Promise((resolve, reject) =>{

}; 

So we want to define a myPromise, first we define a class, and create it after the following code definition

class myPromise {

}

const mp = new myPromise((resolve, reject) => {}) 

You can see that a function is filled in when creating, and this function will actually be passed into the constructor of the myPromise class as a parameter, as follows

class myPromise {constructor(executor) {executor();}
}

const mp = new myPromise((resolve, reject) => {console.log("test");
}) 

That is, the constructor receives console.log("test")this function parameter, and then further executor()calls

And the function we passed in resolvehas rejecttwo parameters and , and this needs to be passed in after being defined in the myPromise class, the code is as follows

class myPromise {constructor(executor) {executor(this.resolve,this.reject);}resolve() {}reject() {}
} 

So in this way we pass the two methods in myPromise to the two parameters of the function in the constructor

Another problem here is that we want resolve to be called as a private method, so add#

So, the end result of defining the myPromise class is as follows

class myPromise {constructor(executor) {executor(this.#resolve,this.#reject);}#resolve(value) {}#reject(reason) {}
} 

Write accessors that execute synchronously

After defining myPromise, it is necessary to realize the function of Promise. The first one is to be able to store data through resolve

This requires defining a variable in myPromise result, and then assigning it through the resolve method, as follows

class myPromise {#result;constructor(executor) {executor(this.#resolve,this.#reject);}#resolve(value) {this.#result = value;}
}

const mp = new myPromise((resolve, reject) => {resolve("test")
})
console.log(mp); 

However, after execution, it was found that an error was reported

image.png

This involves a problem pointed to by this, which will be discussed in detail in future articles, and here we need to bind this to myPromise

 constructor(executor) {executor(this.#resolve.bind(this),this.#reject.bind(this));} 

In this way, we have successfully bound the resolve method to myPromise, and executed it again to see that the data has been stored

image.png

However, there is still a problem here. For the original Promise, multiple resolutions are executed. Due to the existence of PromiseState, only the stored value of the first resolution will be retained. However, performing similar operations on our current myPromise will be continuously overwritten

So we need to introduce a state variable. If it is in the pending state, state = 0; in the fulfilled state, state = 1; in the rejected state, state = -1.

So when resolve is called, if it is not equal to pending, the execution will not continue; if it is equal to pending, the state will be assigned a value of 1 after the storage is executed.

#resolve(value) {if(!this.#state) return ;this.#result = value;this.#state = 1; 
} 

In this way, we can achieve non-duplicated storage

Once the data is stored, we can proceed to read the data

Define a then method as follows

then(onFulfilled, onRejected){if (this.#state === 1){onFulfilled(this.#result);}
} 

Return the corresponding result or reason by judging the state of the state

So in this way we have completed the access of the synchronous execution code

Access to asynchronously executed code

Although above we have realized the access of synchronous execution code, the charm of Promise is in processing asynchronous data

When we use setTimeoutit for resolve storage, it is not surprising that the data cannot be read, as follows

const mp = new myPromise((resolve, reject) => {setTimeout(() => {resolve("test");},100);
}) 

The reason is that when the then method is called, the resolve method has not yet been executed, and the value of the state is still the initial value 0, so it is impossible to enter the judgment

Let's ignore the problem that the then method is executed before the resolve method, and let the program run normally.

So the problem now is to let the program call the callback function onFulfilled() again after the data is passed into resolve, so as to pass the data out

So define a callback as a callback function, and store onFulfilled when the then method is executed, as follows

if (this.#state === 0) {this.#callback = onFulfilled;
} 

Then call the callback callback function in the resolve method, as follows

 #resolve(value) {if (this.#state !== 0) return;this.#result = value;this.#state = 1;this.#callback(this.#result);} 

In this way, we solve the problem at this time

For further optimization, write resolve as follows

 #resolve(value) {if (this.#state !== 0) return;this.#result = value;this.#state = 1;this.callback && this.#callback(this.#result);} 

A short-circuit mechanism of this.callback is added to resolve to prevent callback from being undefined when the then method is not called;

Optimize task queue

When we need to use the then method multiple times to read the value in myPromise, as follows

const mp = new MyPromise((resolve, reject) => {setTimeout(() => {resolve("test");}, 1000)
})

mp.then((result)=>{console.log("result1",result);
})

mp.then((result)=>{console.log("result2",result);
})

mp.then((result)=>{console.log("result3",result);
}) 

The result obtained is only result3 of the last run. This is because the callback function is overwritten every time the then method is called, so that the code can only run the last overwritten case.

So we can define callback as an array of functions,#callbacks = [];

Then for saving the callback in then, it should be changed to add a callback to the array, as follows

this.#callbacks.push(() => {onFulfilled(this.#result);
}) 

Similarly, calling callbacks should also traverse each element of the array, as follows

this.#callbacks.forEach(cb => {cb();
}) 

This way we can execute each then

However, in optimizing the task queue, we know that the original Promise will put the task into the microtask queue, so we should do the same, by putting the task queueMicrotaskinto the microtask queue, as follows

#resolve(value) {if (this.#state !== 0) return;this.#result = value;this.#state = 1;queueMicrotask(() => {this.#callbacks.forEach(cb => {cb();})})
} 

Implement the chain call of then

Now our code obviously cannot implement chained calls, because the premise of chained calls is that each time the then method is called, it also returns a Promise, and myPromise.then has no return value

So we need to let then have a return value, as follows

then(onFulfilled, onRejected) {return new MyPromise((resolve, reject) => {if (this.#state === 0) {this.#callbacks.push(() => {resolve(onFulfilled(this.#result));})}else if (this.#state === 1) {queueMicrotask(() => {resolve(onFulfilled(this.#result));})}})
} 

At this time, the return value in the callback function in then will become the data in the new myPromise, that is, it will be stored again through resolve onFulfilled(this.#result), so that we can get a new processed myPromise

So in this way we can realize the chain call of myPromise

at last

Recently, I also sorted out a JavaScript and ES note, a total of 25 important knowledge points, and explained and analyzed each knowledge point. It can help you quickly master the relevant knowledge of JavaScript and ES, and improve work efficiency.



Friends in need, you can click the card below to receive and share for free

Guess you like

Origin blog.csdn.net/qq_53225741/article/details/129379409