Hand tear promise source code

Promises
are very important. The newly added native APIs and front-end frameworks use promises extensively. Promises have become the "water" and "electricity" of the front-end.

What problem does promise solve? Promise solves the problem of asynchronous coding style.

Let's see, our asynchronous code used to look like this:

let fs = require('fs');
 
fs.readFile('./dellyoung.json',function(err,data){   fs.readFile(data,function(err,data){     fs.readFile(data,function( err,data){       console.log(data)     })   }) }) are nested layer by layer, interlocking. It is already strenuous enough to get the callback result, but if you want to do error handling. . . That was just too hard.






After the promise appears, these problems are easily solved:

let fs = require('fs');
 
function getFile(url){
  return new Promise((resolve,reject)=>{
    fs.readFile(url,function(error,data){
        if(error){
            reject(error)
        }
        resolve(data)
    })
  })
}
 
getFile('./dellyoung.json').then(data=>{
    return getFile(data) 
}).then(data=>{
    return getFile(data)  
}).then(data=>{
    console.log(data)
}).catch(err=>{
    // 统一错误处理
    console.log(err)
})

It's just so easy to use.

It can be found that the use of promise solves the problems of nested calls and error handling of asynchronous callbacks.

Everyone already knows that promises are very important, but how to fully learn promises? After tearing the promise by hand, it will naturally be understood. Let's start tearing it, and we will peel it off in the process.

promise/A+ specification
We want to write a promise now, but who will tell what is a qualified promise? Don’t worry, the industry implements promises through a rule indicator. This is Promise/A+. There is also a translation for reference 【Translation】Promises / A+ specification.

Let's start to implement it step by step!

Synchronous promises
start with the simplest promise implementation

The constructor
implements the foundation of the promise first: the constructor for initialization

class ObjPromise {     constructor(executor) {         // Promise status         this.status = 'pending';         // The resolve callback is successful, the parameter value in the resolve method         this.successVal = null;         // The reject callback is successful, the parameter value in the reject method         this.failVal = null;         // define resolve function         const resolve = (successVal) => {             if (this.status !== 'pending') {                 return;             }             this.status = 'resolve';             this.successVal = successVal;         };         // define reject         const reject = (failVal) => {             if (this.status !== 'pending') {







 








 



                return;
            }
            this.status = 'reject';
            this.failVal = failVal;
        };
 
        try {             // Give the resolve function to the user             executor(resolve, reject)         } catch (e) {             // When the execution throws an exception             reject( e)         }     } }







Let's first write a constructor to initialize promise.

Next analyze:

Call ObjPromise to pass in a function named executor. The executor function accepts two parameters, resolve and reject, which can be understood as representing the call when it succeeds and the call when it fails. The executor function generally looks like this (resolve,reject)=>{...}

status represents the state of the current promise, there are three kinds of 'pending', 'resolve', 'reject' (note: if considered from the state machine, there is an additional initial state, indicating that the promise has not yet been executed)

successVal and failVal respectively represent the parameter values ​​carried by the resolve callback and the reject callback

Function resolve: When initialized, it is passed to the user as an executor parameter, which is used to call when the user needs it, and the status status is changed from 'pending' to 'resolve'

Function reject: Pass it to the user as an executor parameter during initialization, and change the status from 'pending' to 'reject'

You may also find that there are if (this.status !== 'pending') {return;} in the function resolve and the function reject. This is because resolve or reject can only be called once, that is, the status state can only be changed once.

then method
then method function: get the value of resolve or reject in promise.

1. The basic version of then method

Put the following then method in the class:

then(onResolved, onRejected) {
    switch (this.status) {
        case "resolve":
            onResolved(this.successVal);
            break;
        case "reject":
            onRejected(this.failVal);
            break;
    }
}
来分析一下:

The then method can pass in two parameters, both of which are functions, and the two functions are like this (val)=>{...}

When the status is 'resolve', the first function passed in is called, and the val passed in is successVal

When the status is 'reject', call the second incoming function, and the incoming val is failVal

But the then method also needs to support chain calls, that is to say, it can be like this:

new Promise((resolve,reject)=>{     resolve(1); }).then((resp)=>{     console.log(resp); // 1 }).then(()=>{    ... }) 2. Make the then method support chain calls






In fact, the core of supporting chaining is that the then method returns a new promise. Let's modify it to support chaining calls.

then(onResolved, onRejected) {     // To return a promise object     let resPromise;     switch (this.status) {         case "resolve":             resPromise = new ObjPromise((resolve, reject) => {                 try{                     // The first passed A function                     onResolved(this.successVal);                     resolve();                 }catch (e) {                     reject(e);                 }             });             break;         case "reject":             resPromise = new ObjPromise((resolve, reject) => {                 try {                     // The second function passed in


















                    onRejected(this.failVal);
                    resolve();
                }catch (e) {                     reject(e);                 }             });             break;     }     return resPromise; } Analyze again:







When the status is 'resolve', pass the successVal result of the successful resolution of the promise to the first method onResolved(), and then execute the onResolved(this.successVal) function

When the status is 'reject', the process continues, so I won't say more

The key point is that they will assign the newly created promise to the then method. After execution, the then method will return the new promise, so that the chain call of then can be realized.

3. Make the chain call of the then method pass parameters

But you didn't find a problem. The first parameter in my then method, that is, the onResolved() function, the return value inside the function should be able to be passed to the then method that is followed by chain calls, as shown below:

new Promise((resolve,reject)=>{     resolve(1); }).then((resp)=>{     console.log(resp); // 1     return 2; // <<< focus on this line }) .then((resp)=>{    console.log(resp); // 2 received the parameter 2 }) How can this be achieved?







it's actually really easy:

then(onResolved, onRejected) {     // Define this variable to save the promise object to be returned     let resPromise;     switch (this.status) {         case "resolve":             resPromise = new ObjPromise((resolve, reject) => {                 try{                     // The first function passed in                     let data = onResolved(this.successVal);                     resolve(data);                 }catch (e) {                     reject(e);                 }             });             break;         case "reject":             resPromise = new ObjPromise(( resolve, reject) => {                 try{
 


    















                    // The second function passed in
                    let data = onRejected(this.failVal);
                    resolve(data);
                }catch (e) {                     reject(e);                 }             });             break;     }     return resPromise; } is simple:







First save the result of function execution, that is, the return value of the function

Then, pass the return value to resolve() of the new promise to return, and you can save the return value to the successVal of the new promise

If there is an error in execution, of course, the error must be passed to reject() of the new promise that is used to return, and the error will be saved to the failVal of the new promise

4.then incoming parameter processing

Look at this common code again:

new Promise((resolve,reject)=>{     resolve(1); }).then((resp)=>{     console.log(resp); // 1     return 2;  }).then((resp)=> {    console.log(resp); // 2 }) As you can see, only one parameter of the then method can be passed, and continue to be modified:







then(onResolved, onRejected) {
    const isFunction = (fn) => {
        return Object.prototype.toString.call(fn) === "[object Function]"
    };
    onResolved = isFunction(onResolved) ? onResolved : (e) => e;
    onRejected = isFunction(onRejected) ? onRejected : err => {
        throw err
    };
    ······
}
分析一下:

Determine whether the type of the incoming parameter is a function

If the incoming type is a function, then there is nothing wrong with it, just use it directly

If the input type is not a function, that's bad, we have to use (e) => e and (err) => {throw err} to replace

Up to now promise has been able to work normally, the code is as follows:

class ObjPromise {     constructor(executor) {         // Promise status         this.status = 'pending';         // The resolve callback is successful, the parameter value in the resolve method         this.successVal = null;         // The reject callback is successful, the parameter value in the reject method         this.failVal = null;         // define resolve function         const resolve = (successVal) => {             if (this.status !== 'pending') {                 return;             }             this.status = 'resolve';             this.successVal = successVal;         };         // define reject         const reject = (failVal) => {             if (this.status !== 'pending') {







 








 



                return;
            }
            this.status = 'reject';
            this.failVal = failVal;
        };
 
        try {             // Give the resolve function to the user             executor(resolve, reject)         } catch (e) {             // When the execution throws an exception             reject( e)         }     }     then(onResolved, onRejected) {         const isFunction = (fn) => {             return Object.prototype.toString.call(fn) === "[object Function]"         };         onResolved = isFunction(onResolved) ? onResolved : (e) => e;         onRejected = isFunction(onRejected) ? onRejected : err => {







 






            throw err
        };
 
        // Define this variable to save the promise object to be returned
        let resPromise;
 
        switch (this.status) {             case "resolve":                 resPromise = new ObjPromise((resolve, reject) => {                     try{                         // incoming The first function                         let data = onResolved(this.successVal);                         resolve(data);                     }catch (e) {                         reject(e);                     }                 });                 break;             case "reject":                 resPromise = new ObjPromise((resolve, reject ) => {













                    try{                         // The second function passed in                         let data = onRejected(this.failVal);                         resolve(data);                     }catch (e) {                         reject(e);                     }                 });                 break;         }         return resPromise;     } } You You can run the following test code in the console:












new ObjPromise((resolve,reject)=>{     resolve(1); }).then((resp)=>{     console.log(resp); // 1     return 2;  }).then((resp)=> {    console.log(resp); // 2 }) The console will print out 1 2 in sequence.







5.then return value processing

Synchronizing the promise code is no problem now, but it is not enough, because Promise/A+ stipulates that the then method can return any value, including the Promise object of course, and if it is a Promise object, we need to disassemble it until it is not a Promise object, take its value.

Because such processing is required when the status is 'resolve' and 'reject', we can encapsulate the processing process into a function, the code is as follows:

then(onResolved, onRejected) {
    ···
    let resPromise;
 
    switch (this.status) {
        case "resolve":
            resPromise = new ObjPromise((resolve, reject) => {
                try {
                    // 传入的第一个函数
                    let data = onResolved(this.successVal);
                    this.resolvePromise(data, resolve, reject);
                } catch (e) {
                    reject(e);
                }
            });
            break;
        case "reject":
            resPromise = new ObjPromise((resolve, reject) => {
                try {
                    // The second function passed in
                    let data = onRejected(this.failVal);
                    this.resolvePromise(data, resolve, reject);
                } catch (e) {                     reject(e);                 }             });             break;     }     return resPromise ; } // data is the return value // newResolve is the resolve method of the new promise // newReject is the reject method of the new promise resolvePromise(data, newResolve, newReject) {     // Judging whether it is a promise or not directly resolving     if( !(data instanceof ObjPromise)){         return newResolve(data)     }     try {         let then = data.then;







 










        const resolveFunction = (newData) => {             this.resolvePromise(newData, newResolve, newReject);         };         const rejectFunction = (err) => {             newReject(err);         };         then.call(data, resolveFunction, rejectFunction)     } catch (e) {         // Error handling         newReject(e);     } } Analyze:











Determine the return value type, if it is not a promise, just resolve it directly

When it is a promise type, use this.resolvePromise(newData, newResolve, newReject) to call the then method recursively until the data is not a promise, and then resolve the result.

6. Solve the circular reference of then return value

Now there is another problem:

If the new promise has a circular reference, it will never recurse to the end

Take a look at executing the following code:

let testPromise = new ObjPromise((resolve, reject) => {     resolve(1); }) let testPromiseB = testPromise.then((resp) => {     console.log(resp); // 1     return testPromiseB; }) will Error stack overflow.






The solution to this problem is: pass the current new promise object to the resolvePromise() method, and judge that the current new promise object is different from the return value of the function execution.

class ObjPromise {     constructor(executor) {         // Promise status         this.status = 'pending';         // The resolve callback is successful, the parameter value in the resolve method         this.successVal = null;         // The reject callback is successful, the parameter value in the reject method         this.failVal = null;         // define resolve function         const resolve = (successVal) => {             if (this.status !== 'pending') {                 return;             }             this.status = 'resolve';             this.successVal = successVal;         };         // define reject         const reject = (failVal) => {             if (this.status !== 'pending') {







 








 



                return;
            }
            this.status = 'reject';
            this.failVal = failVal;
        };
 
        try {             // Give the resolve function to the user             executor(resolve, reject)         } catch (e) {             // When the execution throws an exception             reject( e)         }     }     resolvePromise(resPromise, data, newResolve, newReject) {         if (resPromise === data) {             return newReject(new TypeError('Circular reference')) }         if         (!(data instanceof ObjPromise)) {             return newResolve( data)         }         try {







 








            let then = data.then;
            const resolveFunction = (newData) => {
                this.resolvePromise(resPromise, newData, newResolve, newReject);
            };
            const rejectFunction = (err) => {
                newReject(err);
            };
            then.call(data, resolveFunction, rejectFunction)
        } catch (e) {
            // 错误处理
            newReject(e);
        }
    }
 
    then(onResolved, onRejected) {
        const isFunction = (fn) => {
            return Object.prototype.toString.call(fn) === "[object Function]"
        };
        onResolved = isFunction(onResolved) ? onResolved : (e) => e;
        onRejected = isFunction(onRejected) ? onRejected : err => {             throw err         };         // Define this variable to save the promise object to be returned         let resPromise;         switch (this .status) {             case "resolve":                 resPromise = new ObjPromise((resolve, reject) => {                     try {                         // the first function passed in                         let data = onResolved(this.successVal);                         this.resolvePromise(resPromise, data , resolve, reject);                     } catch (e) {                         reject(e);


 











                    }
                });
                break;
            case "reject":
                resPromise = new ObjPromise((resolve, reject) => {                     try {                         // The second function passed in                         let data = onRejected(this.failVal);                         this.resolvePromise(resPromise , data, resolve, reject);                     } catch (e) {                         reject(e);                     }                 });                 break;         }         return resPromise;     } } You can try the following code in the console:













new ObjPromise((resolve, reject) => {     resolve(1); }).then((resp) => {     console.log(resp); // 1     return 2 }).then((resp) => {     console.log(resp); // 2     return new ObjPromise((resolve, reject) => {         resolve(3)     }) }).then((resp) => {     console.log(resp); // 3 } ); the console will print out 1 2 3 at a time












Asynchronous promise
Now we have implemented a synchronous version of promise, but in many cases, the resolve or reject of promise is called asynchronously. If it is called asynchronously, when the then() method is executed, the current status is still 'pending'. How does this improve the code?

The idea is actually very simple:

Set up two arrays and store them respectively in the callback functions onResolved and onRejected of the then() method

When resolve or reject is called, just execute the callback function stored in the corresponding array

In addition, in order to ensure the execution sequence and wait for the current execution stack to complete, we also need to wrap the resolve and reject functions of the constructor with setTimeout to avoid affecting the currently executing tasks.

Transform the promise according to this idea:

class ObjPromise {     constructor(executor) {         // Promise status         this.status = 'pending';         // The resolve callback is successful, the parameter value in the resolve method         this.successVal = null;         // The reject callback is successful, the parameter value in the reject method         this.failVal = null;         // callback function of resolve         this.onResolveCallback = [];         // callback function of reject         this.onRejectCallback = [];         // define resolve function         const resolve = (successVal) => {             setTimeout(() =>{                 if (this.status !== 'pending') {                     return;                 }                 this.status = 'resolve';







 




 







                this.successVal = successVal;
 
                //Execute all resolve callback functions
                this.onResolveCallback.forEach(fn => fn())
            })
        };
 
        //Define reject
        const reject = (failVal) => {             setTimeout(()=> {                 if (this.status !== 'pending') {                     return;                 }                 this.status = 'reject';                 this.failVal = failVal;                 //Execute all reject callback functions                 this.onRejectCallback.forEach(fn => fn( ))             })         };         try {             // Give the resolve function to the user






 




 


            executor(resolve, reject)
        } catch (e) {             //             reject(e)         }     }     // data is the return value     // newResolve is the resolve method of the new promise     // newReject is the rejection of the new promise Method     resolvePromise(resPromise, data, newResolve, newReject) {         if (resPromise === data) {             return newReject(new TypeError('Circular Reference')) }         if         (!(data instanceof ObjPromise)) {             return newResolve(data)         }         try {             let then = data. then;             const resolveFunction = (newData) => {




 













                this.resolvePromise(resPromise, newData, newResolve, newReject);
            };
            const rejectFunction = (err) => {
                newReject(err);
            };
            then.call(data, resolveFunction, rejectFunction)
        } catch (e) {
            // 错误处理
            newReject(e);
        }
    }
 
    then(onResolved, onRejected) {
        const isFunction = (fn) => {
            return Object.prototype.toString.call(fn) === "[object Function]"
        };
        onResolved = isFunction(onResolved) ? onResolved : (e) => e;
        onRejected = isFunction(onRejected) ? onRejected : err => {             throw err         };         // Define this variable to save the promise object to be returned         let resPromise;         switch (this.status) {             case "resolve":                 resPromise = new ObjPromise((resolve , reject) => {                     try {                         // the first function passed in                         let data = onResolved(this.successVal);                         this.resolvePromise(resPromise, data, resolve, reject);                     } catch (e) {                         reject(e) ;                     }                 });                 break;


 














            case "reject":
                resPromise = new ObjPromise((resolve, reject) => {
                    try {
                        // 传入的第二个函数
                        let data = onRejected(this.failVal);
                        this.resolvePromise(resPromise, data, resolve, reject);
                    } catch (e) {
                        reject(e);
                    }
                });
                break;
            case "pending":
                resPromise = new ObjPromise((resolve, reject) => {
                    const resolveFunction = () => {
                        try {
                            // The first function passed in
                            let data = onResolved(this.successVal);
                            this.resolvePromise(resPromise, data, resolve, reject);
                        } catch (e) {                             reject(e);                         }                     };                     const rejectFunction = ( ) => {                         try {                             // The second function passed in                             let data = onRejected(this.failVal);                             this.resolvePromise(resPromise, data, resolve, reject);                         } catch (e) {                             reject(e);










                        }
                    };
                    this.onResolveCallback.push(resolveFunction);
                    this.onRejectCallback.push(rejectFunction);
                });
                break;
        }
        return resPromise;
    }
}
You can test it with the following code:

new ObjPromise((resolve, reject) => {     setTimeout(() => {         resolve(1);     }, 100) }).then((resp) => {     console.log(resp); // 1     return 2 }).then((resp) => {     console.log(resp); // 2     return new ObjPromise((resolve, reject) => {         resolve(3)     }) }).then((resp) => {     console.log(resp); // 3 }); We are now basically done with Promise's then method.














Perfecting promise
Up to now, the two core methods of promise have been completed: constructor method and then method. But Promise/A+ also stipulates some other methods, let's continue to complete.

The catch method
catch() method is to get the value of reject through the callback function. This is easy to handle. In fact, the then method has been implemented. Just switch to the then method:

catch(onRejected) {     return this.then(null, onRejected) } This implements the catch() method


Promise.resolve()/reject() method
Everyone must have seen Promise.resolve() or Promise.resolve() usage. In fact, the function is to return a new promise and call resolve or reject internally.

ObjPromise.resolve = (val) => {     return new ObjPromise((resolve, reject) => {         resolve(val)     }) }; ObjPromise.reject = (val) => {     return new ObjPromise((resolve, reject) = > {         reject(val)     }) }; Through these two methods, we can easily convert existing data into promise objects




 





all method
The all method is also a very common method. It can pass in the promise array. When all resolve or there is a reject, the execution ends. Of course, the return is also a promise object. Let's realize it.

ObjPromise.all = (arrPromise) => {     return new ObjPromise((resolve, reject) => {         // The incoming type must be an array         if(Array.isArray(arrPromise)){             return reject(new TypeError("Incoming type Must be an array"))         }         // resp saves the execution result of each promise         let resp = new Array(arrPromise.length);         // saves the number of completed promises         let doneNum = 0;         for (let i = 0; arrPromise. length > i; i++) {             // Change the current promise             let nowPromise = arrPromise[i];             if (!(nowPromise instanceof ObjPromise)) {                 return reject(new TypeError("Type Error"))             }















            // Store the execution result of the current promise in then
            nowPromise.then((item) => {                 resp[i] = item;                 doneNum++;                 if(doneNum === arrPromise.length){                     resolve(resp);                 }             } , reject)         }     }) }; to analyze:









Pass in the promise array and return a new Promsie object

resp is used to save the execution results of all promises

Use instanceof to determine whether it is a promise type

Get the return value by calling the then method of each promise, and pass it to the reject method

Use doneNum to save the number of promises that have been executed. After all executions, pass the execution result resp through resolve, and change the current promise state to 'resolve', and then you can use the then method to get the value

The race method
The race method is also used occasionally. It can pass in a promise array. When any promise is executed, the race will be executed directly. Let's implement it:

ObjPromise.race = (arrPromise) => {     return new Promise((resolve, reject) => {         for (let i = 0; arrPromise.length > i; i++) {             // change current promise             let nowPromise = arrPromise[i] ;             if (!(nowPromise instanceof ObjPromise)) {                 return reject(new TypeError("TypeError"))             };             nowPromise.then(resolve, reject);         }     }) }; Let's analyze:











Pass in the promise array and return a new Promsie object

Use instance to judge whether it is a promise type

Call the then method of each promise, and pass the resolve and reject methods, whichever is executed first will end directly, and the subsequent value can be obtained through the then method

OK, now we have implemented a promise object of our own!
————————————————
Copyright statement: This article is an original article of CSDN blogger "winty~~", following the CC 4.0 BY-SA copyright agreement, please attach the original source link and this statement.
Original link: https://blog.csdn.net/LuckyWinty/article/details/107970255

Guess you like

Origin blog.csdn.net/gcyaozuodashen/article/details/128100013