• 手写Promise看着一篇就足够了


     

     
    目录
    • 概要
    • 博客思路
    • API的特性与手写源码
      • 构造函数
      • then
      • catch
      • Promise.resolved
      • Promise.rejected
      • Promise.all
      • Promise.race

    概要

    本文主要介绍了Promise上定义的api的特性,以及手写如何实现这些特性。目的是把学习过程中的产出以博客的方式输出,巩固知识,也便于之后复习

    博客思路

    mdn上搜索Promise,了解类和api的定义:

    • 定义了哪些属性,分别代表什么含义
    • api需要传什么参数,返回什么值,可能抛出什么异常
    • 看官方给出的用例,猜想内部可能的实现
    • 编写源码,用官方用例验证查看返回值是否一致

    API的特性与手写源码

    构造函数

    • promise有状态pending、rejectedresolved,所以应该有个变量来保存状态
    • 构造函数参数excutor是个同步执行的回调函数,函数执行的参数是两个函数resolved和rejected,所以promise内部需要定义两个函数,并且在构造行数中执行excutor的地方传入
    • .then中会传入回调函数onResolved和onRejected,在resolved和rejected内会分别会触发对应的回调函数,所以需要两个数组保存then中传进来的回调
    • resolved和rejected只能执行一次,执行后promise的状态会改变,且参数会传递给回调函数
    • onRejected和onResolved异步执行
    • excutor执行抛异常会直接执行rejected,所以excutor的执行需要catch错误
    
    const PENDING = "PENDING";
    const RESOLVED = "resolved";
    const REJECTED = "rejected";
    
    function MyPromise(excutor){
    
        // promise内部保存着状态值
        this.status = PENDING;
        this.data = null;
        // then方法传进来的回调函数此保存
        this.onResolvedList = [];
        this.onRejectedList = [];
    
        let resolved = (value) => {
            // resolved函数只能执行一次,所以先判断状态是不是pending
            if(this.status !== PENDING){
                return;
            }
            // 变更状态为resolved
            this.status = RESOLVED;
            // 数据为传进来的值
            this.data = value;
    
            // 判断是否已经有onResolved回调已经穿入,有则异步执行
            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{
            // 执行器函数同步执行,且参数为promise内定义的resolve和rejected
            excutor(resolved, rejected);
        }catch(error){
            // 如果执行器函数出错直接执行rejected
            this.rejected(error);
        }
    }
    

    then

    • then会接受两个回调函数onResolved和onRejected
    • onResolved和onRejected会异步调用
    • then会返回一个新的promise对象
    • then的参数如果没传,那么value和reason继续向下传递
    • 如果执行then的时候,promise的状态还是pending,那么只保存回调,并且确保回调执行后能修改新的promise的状态
    • 如果触发的对应的回调函数执行抛异常,那么返回的新的回调函数状态为rejected,reason则会catch到的error
    • 如果触发的对应回调函数执行返回值不是promise对象,那么返回新的promise状态为resolved,value则为传入then的回调的返回值
    • 如果触发的对应回调返回值是promise对象,那么新的promise返回值的状态取决于改回调返回的promise
    MyPromise.prototype.then = function(onResolved, onRejected){
        
        // 如果没有传onResolved,则设置onResolved为返回value的函数
        onResolved = typeof onResolved === "function" ? onResolved : value => value
        // 如果没有传onRejected,则设置onRejected为抛处reason的函数
        onRejected = typeof onRejected === "function" ? onRejected : reason => {throw reason}
    
        return new MyPromise((resolved, rejected) => {
    
            // 传入要执行的回调函数
            let callBackExcu = (callback) => {
                try{
                    let result = callback(this.data);
                    if(result instanceof MyPromise){
                        // 如果回调返回值还是promise则then返回的promise的状态取决于回调的返回的promise,成功就执行resolved失败就执行rejected
                        result.then(resolved, rejected);
                    }else{
                        // 如果回调的返回值不为promise则新的promise状态为resolved
                        resolved(result)
                    }
                }catch(error){
                    // 如果回调执行抛处异常,则新的promise状态为rejected
                    rejected(error);
                }
            }
    
            if(this.status === PENDING){
                // 如果状态为pending则保存回调且确保回调执行后能修改当前返回的promise的状态
                this.onResolvedList.push((value) => {
                    callBackExcu(onResolved)
                });
                this.onRejectedList.push((reason) => {
                    callBackExcu(onRejected)
                });
            }else{
                // 如果状态不为pending则根据状态执行对应的回调,且修改当前promise的状态
                switch(this.status){
                    case REJECTED:
                        // onRejected异步执行
                        setTimeout(() => {
                           callBackExcu(onRejected); 
                        });
                        break;
                    case RESOLVED:
                        // onResolved异步执行
                        setTimeout(() => {
                           callBackExcu(onResolved); 
                        });
                        break;
                }
            }
        });
    }
    

    catch

    catch和then其实差不多,不同点在于传入的参数只有onRejected,所以

    MyPromise.prototype.catch = function(onRejected){
        // catch与then的不同点在于传入的参数不一样,不需要传onResolve
        return this.then(null, onRejected);
    }
    

    Promise.resolved

    • resolved会返回一个promise对象
    • 如果传入的参数本就是一个primise对象则直接返回
    • 如果是一个包含“then”方法的对象,返回新的promise对象,且状态取决于then函数的执行,如果then的执行中抛错,则新的promise状态为rejected
    • then的参数为两个回调函数resolved和rejected
    • 如果传入参数value既不是promise的实例,也不是具备then函数的对象,则返回一个新的promise对象且改对象data就为value
    MyPromise.resolve = function(value){
        if(value instanceof MyPromise){
            //  如果传入的参数本就是一个primise对象则直接返回
            return value;
        }else if(typeof value.then === "function"){
            return new MyPromise((resolved, rejected) => {
                try{
                    // then的参数为两个回调函数resolved和rejected
                    value.then(resolved, rejected);
                }catch(error){
                    // 如果then的执行中抛错,则新的promise状态为rejected
                    rejected(error);
                }
            });
        }else{
            // 如果传入参数value既不是promise的实例
            return new MyPromise((resolved, rejected) => {
                resolved(value);
            });
        }
    }
    

    Promise.rejected

    • 接受参数reason,返回一个状态为rejected的data为reason的promise实例
    MyPromise.reject = function(reason){
        return new MyPromise((resolved, rejected) => {
            rejected(reason);
        });
    }
    

    Promise.all

    • 接收的参数是需要满足可迭代协议,否则会抛错
    • 返回值是个promise
    • 如果传入的参数是个空的可迭代的对象,则返回一个状态为resolved的可promise实例,data为空数组,
    Promise.all([]) // Promise {<resolved>: Array(0)}
    Promise.all("") // Promise {<resolved>: Array(0)}
    
    • 如果传入的参数中没有promise实例,或者所有的promise已经是resolved状态,则返回一个promise状态为pending,且异步更新为resolved
    let p = Promise.all([1,2,3,4,Promise.resolve(5)])
    console.log(p); // Promise {<pending>}
    
    • 如果存在promise且状态还是pending,返回一个promise实例,且等所有promise都resolved后,状态更新为resolved,data为传入的顺序

    接下来看下源码

    // 先定义一个验证参数是否满足可迭代协议的方法
    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)){
            // 不满足可迭代协议抛错
            throw new TypeError("Object is not iterable");
        }
    
        let data = [];
        let count = 0;
        // 迭代参数生成数组
        let params = Array.from(iterable);
    
        return new MyPromise((resolved, rejected) => {
            if(params.length === 0){
                // 如果是空的可迭代对象,返回空数组
                resolved(data);
            }else{
                params.forEach((element, index) => {
                    // 遍历每个参数,统一处理成promise的实例
                    // 这样就少了一个逻辑分支
                    let itemPromise = MyPromise.resolve(element);
                    itemPromise.then(
                        value => {
                            // data中的结果需要和传入参数的顺序一致
                            data[index] = value;
                            if(count === params.length - 1){
                                // 说明全都resolved了
                                resolved(data);
                            }
                            count++;
                        },
                        reason => {
                            // reject直接返回
                            rejected(reason);
                        }
                    );
                });
            }
        });
    }
    

    Promise.race

    • 接收一个可迭代对象,这点与方法"all"相同
    • 返回一个新的promise
    • 返回的promise状态为pending,异步更新为resolved
    let p = Promise.race([1,2,3,4]);
    console.log(p); // Promise {<pending>}
    
    p.then(
        value => {
            console.log(value); // Promise{<resolved>: 1}
        }
    );
    
    • 传入的若干promise中,只要有一个promise最先resolved或者rejected,则返回的promise状态更新为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
        }
    );
    

    最后来看一下自己源码的实现

    MyPromise.race = function(iterable){
        if(!isIterable(iterable)){
            // 不满足可迭代协议抛错
            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 => {
                        // 只要有一个promise resolved直接返回
                        resolved(value);
                    },
                    error => {
                        // 只要有一个promise rejected直接返回
                        rejected(error);
                    }
                );
            });
        });
    }
    
  • 相关阅读:
    [洛谷P4774] [NOI2018]屠龙勇士
    [洛谷P3338] [ZJOI2014]力
    [洛谷P1707] 刷题比赛
    svn查看指定版本提交信息的命令
    ajax无刷新上传文件
    给docker里的php安装gd扩展
    PHP基于openssl实现的非对称加密操作
    php获取文件扩展名
    javascript格式化日期
    javascript获取url参数
  • 原文地址:https://www.cnblogs.com/onesea/p/13608800.html
Copyright © 2020-2023  润新知