Promsie source code analysis in one step

Keywords: Javascript Front-end

Broken thoughts:

Dear readers: Hello! My name is Changlon - a non professional programmer, a developer dedicated to the front end, a thinker who loves life and sometimes has melancholy.

If my article can bring you some harvest, your praise collection will be a great encouragement to me!

My email: thinker_changlon@163.com

My Github: https://github.com/Changlon

In this chapter, I will lead you to write a Promise that meets the promise/a + specification. It can be said that this is a technology that front-end engineers must master, which is used very much in the project.
So how is promsie implemented? This article will uncover the mystery of promsie by writing a promsie!
Now please pick up your keyboard and join me in a promise! I believe you will have a deeper understanding of promise after writing!

Before we begin, I would like to ask you to read what is the promsie/a + specification, which specifies how we implement this class.

Related links
promise/a + specification website
myPromise source code github address

1, promsie constructor

Let's write a promise constructor first. Let's think about the new promise ((resolve, reject) = > {}) we usually use promise in this way. We're going to pass an execution function as an argument to the constructor. This execution function is executed when the class is instantiated, that is, the code in this function is executed non asynchronously.

/** promsie Constructor */
export default class promise{ 
    constructor(hanlder) { 
        this.checkHandler(hanlder) ?  
            this.init(hanlder)
        : void 0
    }
    
    /**Initialize promise */
    init(hanlder) {  
    }
    /**Check constructor parameters */
    checkHandler(hanlder) {  
        if(!hanlder) throw new Error('new promsie An argument of function type is required!') 
        if(!(hanlder instanceof Function)) throw new Error('new promsie An argument of function type is required!') 
        return true 
    }

}

In the above code, we first judge whether the correct parameters are passed in the new promise class. If the parameters meet the requirements, we enter the init method to initialize the class. If not, we throw the specified exception.
Next, let's consider how to implement this init method. First, we need to record the instance object, and then define a set of state variables on the instance to determine the state of the current promise. Then execute the handler function.

		//Record the original instance object
		const that = this 
		//Define state variables
        that.pending = "pending"
        that.fulfilled = "fulfilled"
        that.rejected = 'rejected' 
        that.status = this.pending   
        //Defines the value returned by the value fully or rejected status
        that.value = undefined  
        

        /** fulfilled State processing function */
        function onResolved(value) {
        	//If the status changes, the current status cannot be changed
            if(that.status!=that.pending) return void 0 
            that.status = that.fulfilled  
            that.value = value 
        }

        /** rejected State processing function */
        function onRejected(reason) { 
            if(that.status!=that.pending) return void 0 
            that.status = that.rejected  
            that.value = reason
        }
        
        //Use try...catch to catch possible exceptions in user code
        try {
            hanlder(onResolved,onRejected) 
        }catch(e)  {
            throw new Error(`User defined program error:${e}`)
        }

2, then function

Let's review the usage of Promise. When we synchronize the resolve and reject values in the execution function, the then function is also executed synchronously. How to resolve? If the reject value is in asynchronous code, then the then function will be executed after the asynchronous code is executed.
for instance:

//Test example demo1
const p = new promise((resolve,reject)=>{  
    // resolve('test ') / / the' test 'here will be printed immediately
	  setTimeout(()=>{ 
            resolve('test')
      },1000)
})

//At this time, 'test' can only be printed after 1s
p.then(value=>{
    console.log(value)
},reason=>{
    console.log(reason)
})

We found that the then function executes asynchronously or synchronously according to the status state. When the status is' fully 'or' rejected ', the then function executes synchronously. If the status is' pending', it means that the function that changes the status will be executed in the next event cycle. At this time, we also need to put the processing result value functions on fulfilled and onrejected in then into the asynchronous queue for execution.
The simple implementation of the then function is as follows:

 then(onFulfilled,onRejected) {   
        const that = this 
        const value = this.value 
     	switch (that.status) { 
                    case 'pending': 
                        setTimeout(()=>{
                            that.then(onFulfilled,onRejected) 
                        break 
                    case 'fulfilled': 
                            onFulfilled(value) 
                        break 
                    case 'rejected' :
                            onRejected(value)
                        break 
                        
                }
    }

3, then function of promise/a + specification

The above implemented then function can only meet the above test case demo1. To implement a then function conforming to promise/a + specification, we also need to implement the following requirements:

Excerpt from promise/a + website
then must return a promise [3.3].
promise2 = promise1.then(onFulfilled, onRejected);
If either onFulfilled or onRejected returns a value x, run the Promise Resolution Procedure [[Resolve]](promise2, x).
If either onFulfilled or onRejected throws an exception e, promise2 must be rejected with e as the reason.
If onFulfilled is not a function and promise1 is fulfilled, promise2 must be fulfilled with the same value as promise1.
If onRejected is not a function and promise1 is rejected, promise2 must be rejected with the same reason as promise1.

To sum up:
1. The then function returns a promise
2. Return a value in onFulfilled or onRejected. If the value is of non promise type, return the promise object in 'fulfilled' status directly. If it is an object of promise type, return the object directly.
3. If an error is reported in the execution function given by the onFulfilled or onRejected user, a promise in the 'rejected' status must be returned
4. If on fulfilled is not given in a then function and the status is already 'completed', we need to take the status value value as the status value of the returned promise. The same is true for onRejected not given.

Let's analyze how to implement the above requirements. The first point is well implemented. We modify the then function directly

then(onFulfilled,onRejected) {   
        const that = this 
        const value = this.value 
        return new promise((resolve,reject)=>{
			switch (that.status) { 
	                    case 'pending': 
	                        setTimeout(()=>{
	                            that.then(onFulfilled,onRejected) 
	                        break 
	                    case 'fulfilled': 
	                            onFulfilled(value) 
	                        break 
	                    case 'rejected' :
	                            onRejected(value)
	                        break 
	                        
	                }
		})
     	
    }

However, this is not possible. The return is a promise whose status is always' pending '. We need to record the return values of onFulfilled and onRejected, and then resolve the return value.
Modify the code in switch as follows

   case 'fulfilled': 
		 resolve(onFulfilled(value))
		break 
	case 'rejected' :
 		resolve(onRejected(value)) 
		break 

But this still doesn't work. What if the returned promise object is not a js basic data type? Also, onFulfilled and onRejected are functions given by the user. How to handle them if they are not passed in or an exception is reported during execution?
In view of the above problems, we analyze them one by one.
1. What should we do if onfulfilled and onrejected return a promise? We define an traversal function to traverse the promise state until the last state value is the basic data type, and then we resolve or reject the value in the returned promise.
2. What if onfulfilled or onRejected is not passed in? If it is not passed in, we will return it as
The promise status value of is passed down.
3. What if onfulfilled or onRejected reports an exception during execution? We directly include all the executed code in try...catch. Once an error is reported, we can return the promise in the 'rejected' state.

According to the above analysis, we finally modified the code as follows:

  then(onFulfilled,onRejected) {   
        const that = this 
        const value = this.value 
        return new promise((resolve,reject)=>{ 
            try {
                switch (that.status) { 
                    case 'pending': 
                        setTimeout(()=>{
                           const p =  that.then(onFulfilled,onRejected) 
                           p.then(value=>{resolve(value)},reason=>{reject(reason)})
                        })
                        break 
                    case 'fulfilled': 
                            onFulfilled? 
                            //  resolve(onFulfilled(value))  
                            void (()=>{
                                const p = onFulfilled(value) 
                                if(!(p instanceof promise)) return resolve(p)  
                                that.traversePromise(p,resolve,reject)
                            })()
                            : resolve(value)
                            // onFulfilled(value) 
                        break 
                    case 'rejected' :
                            onRejected ?  
                                // resolve(onRejected(value)) 
                            void (()=>{
                                const p = onRejected(value) 
                                if(!(p instanceof promise)) return resolve(p)  
                                that.traversePromise(p,resolve,reject)
                            })()
                            : reject(value) 
                            //onRejected(value)
                        break 
                        
                }
            }catch(e) {
                reject(e) 
            }
          
        })
        
    }

4, Implement catch exception catching function

	//The idea of catch is to catch the exception in the previous then function and return a promise
  catch(onRejected) {
        return this.then(void 0,onRejected) 
  }

5, Implement the resolve and reject static functions

	
	//Returns a promise in 'rejected' status
    static reject(p) { 
        return (p instanceof promise) ? p : new promise((r,j)=>{
            j(p)
        })
    }
    
 	 //Returns a promise in the 'resolved' state
    static resolve(p) {
        return (p instanceof promise) ? p : new promise((r,j)=>{
            r(p)
        })
    }

6, Implement race and all functions

  /** Deal with who is the fastest */
    static race(promsies) { 
        if(!promsies) throw new Error('lack promises parameter!')   
        return new promise((resolve,reject)=>{
            for(let p of promsies) {
                promise.resolve(p)
                .then(value=>{
                    resolve(value)
                })
                .catch(err=>{
                    reject(err)
                })
            }
        })

    }


    /** Process all promises, return results in order in smooth status, return the first error in error status, but the subsequent promises are also executed */
    static all(promsies) {  
        if(!promsies) throw new Error('lack promises parameter!')   
        return new promise((resolve,reject)=>{
            const resolves = [] 
            let counter = 0 
            for(let i = 0 ;i<promsies.length;++i) {   
                const p = promsies[i]
                promise.resolve(p)
                .then(value=>{
                    resolves[i] = value 
                    counter++
                    counter === promsies.length ?  resolve(resolves) : void 0  
                },reason=>{
                     reject(reason)
                })
                
            }
        })
     
    }

The above implements a relatively simple promise version. I believe that through the practice of these codes, you have a deeper understanding of promise! Please give me a compliment when you see here! It's not easy to be original. I didn't come out until the early morning.

Posted by Solar on Fri, 22 Oct 2021 22:49:10 -0700