Handwritten Promise reading one is enough

Turn:

Handwritten Promise reading one is enough

catalogue
  • outline
  • Blog ideas
  • API features and handwritten source code
    • Constructor
    • then
    • catch
    • Promise.resolved
    • Promise.rejected
    • Promise.all
    • Promise.race

outline

This article mainly introduces the features of the api defined on Promise and how handwriting implements these features. The purpose is to output the output in the learning process in the form of blog, consolidate knowledge and facilitate later review

Blog ideas

Search Promise on mdn to understand the definitions of classes and APIs:

  • What attributes are defined and what meanings they represent
  • What parameters the api needs to pass, what values it returns, and what exceptions it may throw
  • Look at the official use cases and guess the possible internal implementation
  • Write the source code and verify with the official use case to see whether the return value is consistent

API features and handwritten source code

Constructor

  • promise has states pending, rejected and resolved, so there should be a variable to save the state
  • The constructor parameter excutor is a callback function executed synchronously. The parameters executed by the function are two functions resolved and rejected. Therefore, two functions need to be defined inside promise and passed in where excutor is executed in the number of construction lines
  • The callback functions onResolved and onRejected will be passed in. Then, and the corresponding callback functions will be triggered in resolved and rejected respectively, so two arrays are needed to save the callbacks passed in then
  • resolved and rejected can only be executed once. After execution, the state of promise will change, and the parameters will be passed to the callback function
  • onRejected and onResolved execute asynchronously
  • Exception throwing by excutor will directly execute rejected, so catch error is required for excutor execution
const PENDING = "PENDING";
const RESOLVED = "resolved";
const REJECTED = "rejected";

function MyPromise(excutor){

    // promise stores the status value internally
    this.status = PENDING;
    this.data = null;
    // The callback function passed in from the then method is saved here
    this.onResolvedList = [];
    this.onRejectedList = [];

    let resolved = (value) => {
        // The resolved function can only be executed once, so judge whether the status is pending first
        if(this.status !== PENDING){
            return;
        }
        // The change status is resolved
        this.status = RESOLVED;
        // The data is the value passed in
        this.data = value;

        // Judge whether there is an onResolved callback that has been penetrated. If so, it will be executed asynchronously
        if(this.onResolvedList.length > 0){
            setTimeout(() => {
                this.onResolvedList.forEach(onResolved => {
                    onResolved(value);
                });
            }, 0);
        }
    }

    let rejected = (reason) => {
        if(this.status !== PENDING){
            return
        }

        this.status = REJECTED;
        this.data = reason;

        if(this.onRejectedList.length > 0){
            setTimeout(() => {
                this.onRejectedList.forEach(onRejected => {
                    onRejected(reason);
                });
            });
        }
    }

    try{
        // The executor functions are executed synchronously, and the parameters are resolve and rejected defined in promise
        excutor(resolved, rejected);
    }catch(error){
        // If the executor function fails, execute rejected directly
        this.rejected(error);
    }
}

then

  • then will accept two callback functions onResolved and onRejected
  • onResolved and onRejected are called asynchronously
  • then returns a new promise object
  • If the parameters of then are not passed, then value and reason continue to be passed down
  • If the promise state is still pending when then is executed, only the callback is saved, and ensure that the state of the new promise can be modified after the callback is executed
  • If the triggered callback function throws an exception, the status of the returned new callback function is rejected, and reason will catch the error
  • If the return value of the triggered corresponding callback function is not a promise object, the return state of the new promise is resolved, and value is the return value of the callback passed in to then
  • If the return value of the triggered corresponding callback is a promise object, the status of the new promise return value depends on the promise returned by the modified callback
MyPromise.prototype.then = function(onResolved, onRejected){
    
    // If onResolved is not passed, set onResolved as the function that returns value
    onResolved = typeof onResolved === "function" ? onResolved : value => value
    // If onRejected is not passed, set onRejected to the function of reason at the disposal
    onRejected = typeof onRejected === "function" ? onRejected : reason => {throw reason}

    return new MyPromise((resolved, rejected) => {

        // Pass in the callback function to execute
        let callBackExcu = (callback) => {
            try{
                let result = callback(this.data);
                if(result instanceof MyPromise){
                    // If the return value of the callback is still promise, the state of the promise returned by then depends on the promise returned by the callback. Resolve will be executed if successful, and reject will be executed if failed
                    result.then(resolved, rejected);
                }else{
                    // If the return value of the callback is not promise, the new promise status is resolved
                    resolved(result)
                }
            }catch(error){
                // If the callback execution throws an exception, the new promise status is rejected
                rejected(error);
            }
        }

        if(this.status === PENDING){
            // If the status is pending, save the callback and ensure that the status of the currently returned promise can be modified after the callback is executed
            this.onResolvedList.push((value) => {
                callBackExcu(onResolved)
            });
            this.onRejectedList.push((reason) => {
                callBackExcu(onRejected)
            });
        }else{
            // If the status is not pending, the corresponding callback is executed according to the status, and the status of the current promise is modified
            switch(this.status){
                case REJECTED:
                    // onRejected asynchronous execution
                    setTimeout(() => {
                       callBackExcu(onRejected); 
                    });
                    break;
                case RESOLVED:
                    // Asynchronous resolve
                    setTimeout(() => {
                       callBackExcu(onResolved); 
                    });
                    break;
            }
        }
    });
}

catch

catch and then are almost the same. The difference is that the only parameter passed in is onRejected, so

MyPromise.prototype.catch = function(onRejected){
    // The difference between catch and then is that the parameters passed in are different. There is no need to pass onResolve
    return this.then(null, onRejected);
}

Promise.resolved

  • resolved returns a promise object
  • If the parameter passed in is a primise object, it will be returned directly
  • If it is an object containing the "then" method, a new promise object is returned, and the state depends on the execution of the then function. If an error is thrown in the execution of then, the new promise state is rejected
  • The parameters of then are the two callback functions resolved and rejected
  • If the passed in parameter value is neither an instance of promise nor an object with the then function, a new promise object is returned and the object data is changed to value
MyPromise.resolve = function(value){
    if(value instanceof MyPromise){
        //  If the parameter passed in is a primise object, it will be returned directly
        return value;
    }else if(typeof value.then === "function"){
        return new MyPromise((resolved, rejected) => {
            try{
                // The parameters of then are the two callback functions resolved and rejected
                value.then(resolved, rejected);
            }catch(error){
                // If an error is thrown during the execution of then, the new promise status is rejected
                rejected(error);
            }
        });
    }else{
        // If the passed in parameter value is neither an instance of promise
        return new MyPromise((resolved, rejected) => {
            resolved(value);
        });
    }
}

Promise.rejected

  • Accept the parameter reason and return a promise instance with the status of rejected and the data of reason
MyPromise.reject = function(reason){
    return new MyPromise((resolved, rejected) => {
        rejected(reason);
    });
}

Promise.all

  • The received parameters need to meet the iterative protocol, otherwise errors will be thrown
  • The return value is promise
  • If the passed in parameter is an empty and iteratable object, a promise instance with the status of resolved is returned, and the data is an empty array,
Promise.all([]) // Promise {
  
  
   
   : Array(0)}
Promise.all("") // Promise {
   
   
    
    : Array(0)}

   
   
  
  
  • If there is no promise instance in the passed in parameter, or all promises are already in resolved status, a promise with pending status will be returned and asynchronously updated to resolved
let p = Promise.all([1,2,3,4,Promise.resolve(5)])
console.log(p); // Promise {
  
  
   
   }

  
  
  • If there is a promise and the status is still pending, a promise instance will be returned. After all promises have been resolved, the status will be updated to resolved and the data will be in the order of incoming

Next, look at the source code

// First, define a method to verify whether the parameters meet the iterative protocol
const isIterable = function(object){
        return typeof object[Symbol.iterator] === "function"
        && typeof object[Symbol.iterator]() === "object"
        && typeof object[Symbol.iterator]().next === "function"
}

MyPromise.all = function(iterable){
    if(!isIterable(iterable)){
        // Fail to satisfy iterative protocol throw error
        throw new TypeError("Object is not iterable");
    }

    let data = [];
    let count = 0;
    // Iterative parameter generation array
    let params = Array.from(iterable);

    return new MyPromise((resolved, rejected) => {
        if(params.length === 0){
            // If it is an empty iteratable object, an empty array is returned
            resolved(data);
        }else{
            params.forEach((element, index) => {
                // Traverse each parameter and uniformly process it into an instance of promise
                // That's one less branch of logic
                let itemPromise = MyPromise.resolve(element);
                itemPromise.then(
                    value => {
                        // The results in data should be in the same order as the parameters passed in
                        data[index] = value;
                        if(count === params.length - 1){
                            // That means it's all resolved
                            resolved(data);
                        }
                        count++;
                    },
                    reason => {
                        // reject returns directly
                        rejected(reason);
                    }
                );
            });
        }
    });
}

Promise.race

  • Receive an iteratable object, which is the same as the method "all"
  • Returns a new promise
  • The returned promise status is pending and asynchronously updated to resolved
let p = Promise.race([1,2,3,4]);
console.log(p); // Promise {
  
  
   
   }

p.then(
    value => {
        console.log(value); // Promise{
   
   
    
    : 1}
    }
);

   
   
  
  
  • If one of the incoming promises is resolved or rejected first, the returned promise status will be updated to resolved
let p1 = new Promise((resolved, rejected) => {
    setTimeout(() => {
        resolved("p1");
    }, 10);
});

let p2 = new Promise((resolved, rejected) => {
    setTimeout(() => {
        resolved("p2");
    }, 100);
});

let p = Promise.race([p2, p1])

p.then(
    value => {
        console.log(value); // p1
    }
);

Finally, let's take a look at the implementation of our source code

MyPromise.race = function(iterable){
    if(!isIterable(iterable)){
        // Fail to satisfy iterative protocol throw error
        throw new TypeError("Object is not iterable");
    }

    const params = Array.from(iterable);

    return new MyPromise((resolved, rejected) => {
        params.forEach((element, index) => {
            const itemPromise = MyPromise.resolve(element);

            itemPromise.then(
                value => {
                    // As long as there is a promise resolved, it returns directly
                    resolved(value);
                },
                error => {
                    // As long as there is a promise rejected, it will be returned directly
                    rejected(error);
                }
            );
        });
    });
}

Turn:

Handwritten Promise reading one is enough


--Posted from Rpc

Added by qumar on Tue, 08 Mar 2022 13:20:57 +0200