zoukankan      html  css  js  c++  java
  • 手写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);
                    }
                );
            });
        });
    }
    
  • 相关阅读:
    Hihocoder 1275 扫地机器人 计算几何
    CodeForces 771C Bear and Tree Jumps 树形DP
    CodeForces 778D Parquet Re-laying 构造
    CodeForces 785E Anton and Permutation 分块
    CodeForces 785D Anton and School
    CodeForces 785C Anton and Fairy Tale 二分
    Hexo Next 接入 google AdSense 广告
    如何统计 Hexo 网站的访问地区和IP
    Design and Implementation of Global Path Planning System for Unmanned Surface Vehicle among Multiple Task Points
    通过ODBC接口访问人大金仓数据库
  • 原文地址:https://www.cnblogs.com/onesea/p/13608800.html
Copyright © 2011-2022 走看看