Handwritten simple Promise

Posted by kritikia on Sat, 05 Mar 2022 17:07:16 +0100

Promise, as a new function in ES6, helps us solve the problem of callback hell and makes our asynchronous code clearer and simpler. As a front-end programmer, handwritten simple promise should be a necessary skill. Next, I won't say much. I'll go directly to the code.

class Commitment {
      static PENDING = 'undetermined'; static FULFILLED = 'success'; static REJECTED = 'refuse';   //  Define Promise status
      constructor(func) {
        this.status = Commitment.PENDING;   // Initial state
        this.result = null   //  The parameter result passed in when calling resolve or reject is null here because the result will be assigned a value when calling resolve or reject
        this.resolveCallbacks = []
        this.rejectCallbacks = []    //  These two arrays are used to store the resolve and reject functions, which are called when there is asynchronous code in then
        
        try {
          func(this.resolve.bind(this), this.reject.bind(this));   //  Attention should be paid to the pointing of this. If this is not bound here, the call will report an error
        } catch (error) {
          this.reject(error)
        }
      }
      resolve(result) {
        setTimeout(() => {
          if (this.status === Commitment.PENDING) {
            this.status = Commitment.FULFILLED;
            this.result = result   // Assign the incoming result to the result of the instance
            this.resolveCallbacks.forEach(callback => {
              callback(result)
            })
          }
        })
      }
      reject(result) {
        setTimeout(() => {
          if (this.status === Commitment.PENDING) {
            this.status = Commitment.REJECTED;
            this.result = result
            this.rejectCallbacks.forEach(callback => {  // Traverse the code to be executed in then, which needs to be used when there are asynchronous operations in then
              callback(result)
            })
          }
        })
      }
      then(onFULFILLEN, onREJECTED) {
        return new Commitment((resolve, reject) => {
          onFULFILLEN = typeof onFULFILLEN === 'function' ? onFULFILLEN : () => {};  //  Only functions can be passed in then. If the passed in function is not a function, it will be assigned as an empty function
          onREJECTED = typeof onREJECTED === 'function' ? onREJECTED : () => {};
          if (this.status === Commitment.PENDING) {
            this.resolveCallbacks.push(onFULFILLEN)
            this.rejectCallbacks.push(onREJECTED)
          }
          if (this.status === Commitment.FULFILLED) {
            setTimeout(() => {
              onFULFILLEN(this.result)
            })
          }
          if (this.status === Commitment.REJECTED) {
            setTimeout(() => {
              onREJECTED(this.result)
            })
          }
        })
      }
    }

There are a lot of codes. Let's look at them step by step. First, use static to define the three states of Promise. status is the initial state of Promise, and set it to PENDING state. Since new Promise needs to pass in a function, func here is the function passed in. The following two arrays are used to deal with asynchronous functions in Promise. We won't talk about them here first.

Because if you throw an error message in Promise, Promise will help you execute the error through the reject function without making your console report an error. Therefore, try catch to capture the error function here. If not, the console will report an error.
In addition, it should be noted that the resolve and reject functions passed in func should bind this with bind, otherwise there will be a problem when calling these two functions through the instance from new.

Because resolve and reject are to be executed at the end of the time cycle, they need to set a layer of setTimeout in the outer layer to turn them into macro tasks.
The if conditional statement is to judge whether the current state is PENDING, because only the PENDING state can change to the full and full states. After judgment, the current state will be changed. Assign the passed parameters to the instance result. The resolveCallbacks here are used to store the functions in asynchronous functions in Promise, traverse these functions and execute them in turn, This is why an empty array is used when defining this value above. The rule of array is first in first out. The same is true for rejectCallbacks below.

Finally, there is the more complex then function, promise The then () function can pass two values here. I compared dishes and didn't know it before. I learned that the first parameter is the function called when it succeeds and the second is the function called when it fails.
Why return an instance of our own Commitment? Because it imitates the chain call of Promise, that is, Promise then(). Then ().
Since then needs to pass in a function, Promise will not report an error if it does not pass in a function when calling then, so here we need to judge the parameters through typeof, and give an empty function if it is not a function, so that an error will not be reported.
The next step is to carry out different logical processing for the three states.

When we call promise like this, we will output 666 instead of 555 first. This is because setTimeout itself is a macro task, and our resolve function also calls setTimeout internally, so it is also a macro task. It is not difficult to understand why we output 666 first when we know the event loop.

The simulation here is that when then is invoked, an asynchronous task is encountered, and the resolve or reject function is invoked asynchronously. At this time, the state can not be changed, so we must deal with the functions in the asynchronous tasks at PENDING and add them to the corresponding Callbacks.

If it is in the success state, use a setTimeout to call back the incoming function and pass the previously saved result parameter to it.
The same goes for failure states.

So far, a simple version of Promise function has been realized. The article is not perfect and is only for your review.

Topics: Javascript Front-end