手写Promise.all和promise.prototype.finally

目录

Promise.all

promise.all特性

Promise.prototype.finally

Promise.prototype.finally特性


  

      大家面试时,面试官可能会让手写一个promise.all和promise.prototype.finally方法,我在手写时候遇到了很多坑,现在记录下来,分享给大家。

Promise.all

        听到让手写一个Promise.all方法,有的同学会这样写

Promise.prototype.all = function () {}

        好多同学第一反应就是这么写,习惯往原型链上去挂一个方法,认为all也是原型链上的方法,很多人都不不用在意这个东西,感觉会实现内部逻辑就可以了,但其实这也是很重要的一项,通过写最开始的这一段赋值,就可以了解到你知不知道什么叫实例方法什么叫静态方法。

        平常使用promise.all时,一般都是去这样调用,其实all就是挂载在Promise这个大对象上的,是一个静态方法,应该这样写。

Promise.all([]).then()

        而实例方法是通过new Promise之后做到的。

new Promise().then()  // 这才是实例方法

        当去实现一个promise.all方法时一定记住不要把all挂载在原型链上,要去实现时,直接这样写就好。

Promise.all = () => { }

        首先知道all是一个静态方法,返回值是一个promise,接受一个参数,参数是数组,整体返回值是一个promise。

Promise.all = (arr) => {
    return new Promise((resolve, reject) => {})
}

promise.all特性

  • 接受一个数组,里面可能是一些promise元素

  • 是要等所有的promise执行完成之后,然后去resolve整体结果

  • 其中一个报错就会reject

        接收到了一个数组,把数组里面完全执行一遍首先想到可以用遍历,便利完之后 里面每一项就是 arr[i],但是有问题,数组里面每一项元素不一定是一个promise,有可能传入的是这么个东西。

Promise.all([1, 2, 3, new Promise()])

这个时候如果直接去 arr[i].then 可能会报错 因为它可能是个简单基本类型,这也有可能是面试官希望考察的一个点。

        基于以上情况,有的同学会这样写。

Promise.all = (arr) => {
    return new Promise((resolve, reject) => {
        for (let i = 0; i < arr.length; i++) {
            if (arr[i] instanceof Promise) {
                // 
            } else {
                // 
            }
        }
    })
}

用 instanceof 判断一下是否是promise,在if和else根据不同类型实现不同逻辑,这样当然能够实现,但是有个问病就是 if 和 else 里面的逻辑几乎是一摸一样的,只不过要做一个类型处理而已,就会导致很大一部分代码的冗余。

        其实这里不推荐去手动判断arr[i]的一个数据类型,既然不知道arr[i]是个什么类型,不如直接把arr[i]强制转换为Promise,这样就比较方便了,可以把所有的逻辑都写在这个Promise的.then或者.catch里面去。

        then里面会拿到一个value,catch里面会拿到一个reason,如果其中有一个报错,就要在.catch里面直接reject。

        .then里面逻辑,首先判断它到底是不是把所有的元素都执行完成,有的同学可能会在for循环上面从以一个结果数组res,然后res.push(value),这样会出现结果数组里面的顺序问题,每当执行完一个arr[i]之后都会把结果value,push到res里面,造成一个结果,数组里面的顺序打乱。

        promise.all的返回结果是按照入参数组的顺序来决定的,一旦使用了push方法(把一个元素一次存进数组的末尾),同步执行是没问题的,但是要知道promise.then是异步的,每一个promise的执行时间是不一定的。

        比如有个数组 [1, 2 ,3 ,4 ,5],1~4的执行时间是100ms,但是5的执行时间是1ms,因为他是异步操作,5肯定是最先执行完成的,那么res数组里面的最后的结果就是[5, 1, 2, 3, 4]。

        如何写才能避免这个问题呢,直接按照索引赋值就不会出现顺序问题。


Promise.all = (arr) => {
    return new Promise((resolve, reject) => {
        let res = [];
        for (let i = 0; i < arr.length; i++) {
            Promise.resolve(arr[i])
                .then((value) => {
                    // res.push(value); 错误写法
                    res[i] = value; // 正确写法
                })
                .catch((reason) => {
                    reject(reason);
                })
        }    })
}

        避免上一个坑之后,有的同学接着又会这样写,res数组的长度和arr数组的长度一样时候,代表为整个promise数组都执行完成了,所有的结果都推进res里面了,直接resolve结果。

        问题:和上个问题一样,5执行时间为1ms,执行完毕后,res[4] = value,这个时候res和arr数组的长度就一样了,这个时候resolve(res),就会导致1~4还没执行完成,res数组里面前四个都是空属性,就把结果返回回去了。

        正确写法:定义一个变量count来计数。

Promise.all = (arr) => {
    return new Promise((resolve, reject) => {
        let res = [];
        let count = 0;
        for (let i = 0; i < arr.length; i++) {
            Promise.resolve(arr[i])
                .then((value) => {
                    res[i] = value;
                    // 错误的写法
                    // if (res.length === arr.length) {    
                    //     resolve(res);
                    // }
                    // 正确写法
                    count ++ 
                    if(count === arr.length) {
                        resolve(res);
                    }
                })
                .catch((reason) => {
                    reject(reason);
                })
        }    })
}

        最后一个点,其他代码都写对了,最后一步resolv(res),写错位置了,这个是最过分的。

Promise.all = (arr) => {
    return new Promise((resolve, reject) => {
        let res = [];
        let count = 0;
        for (let i = 0; i < arr.length; i++) {
            Promise.resolve(arr[i])
                .then((value) => {
                    res[i] = value;
                    count ++ 
                    // 正确写法
                    if(count === arr.length) {
                        resolve(res);
                    }
                })
                .catch((reason) => {
                    reject(reason);
                })
        }
        // 错误写法
        // if(count === arr.length) {
        //     resolve(res);
        // }
    })
}

        最后如果将if判断写在了for循环底下,跟这个错误比起来以上说到的点都算是小问题,这一步如果写到for外面,那么就可以理解为,你对同步以及异步编程没有任何的理解。

        为什么这么说,因为整体代码for循环也好,Promise.resolve也好都是同步执行,而所谓的异步只不过是在.then和.catch的回调里面,如果将resolve(res)写到for循环低下,当for循环执行完成后,直接就会到这一步,根本不会resolve一个数据出去,最终的结果就是一个undefined。

        最后结果代码很少,但是考察的点非常多。

Promise.all = (arr) => {
    return new Promise((resolve, reject) => {
        let res = [];
        let count = 0;
        arr.forEach((item, i) => {
            Promise.resolve(item)
                .then(value => {
                    res[i] = value;
                    count++;
                    if(count === arr.length) resolve(res)
                })
                .catch(reason => {
                    reject(reason);
                })
        })
    })
}

Promise.prototype.finally

        和.all不一样的是,.finally是真的挂载在原型对象上的,是一个实例方法,是一个函数接收一个回调方法。

Promise.prototype.finally特性

  • 无论promise被reslove或者reject,都会执行到finally里面去

        this.then里面要接收两个参数,一个参数是是被resolve的返回值,一个是被reject的回调

Promise.prototype.finally = (callback) => {
    return this.then(
        (value) => {
            return Promise.resolve(callback()).then(() => value)
        },
        (error) => {
            return Promise.reject(callback()).then(() => { throw error })
        }
    )
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

清梦-

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值