手写Promise.all
前言
之前在看远方os大佬直播的时候看到有让手写的Promise.all的问题,然后心血来潮自己准备手写一个
开始
首先,我们需要明确原本js提供的Promise.all的特性
- Promise.all返回的是一个Promise
- 如果传入的数据中有一个reject即整个all返回的就是reject,且是第一个reject的数据
- 如果传入的不是Promise,all会自动用Promise.reslove将其解析成Promise完成状态(重要)
- all的函数不一定非要是数组
那我们用Promise验证一下吧
以上就是all主要的几个特性了,还有特性后面详细描述
编写代码
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>Document</title></head><body><script>Promise.myAll = (data) => {return new Promise((resolve, reject) => {let result = [];let count = 0;const promises = Array.from(data);if (promises.length === 0) {return resolve(result); // 空迭代对象直接 resolve}for (let i = 0; i < promises.length; i++) {Promise.resolve(promises[i]).then((res) => {result[i] = res;count++;if (count === data.length) {resolve(result);}}).catch((err) => {reject(err);});}});};const a = new Promise((resolve) => {setTimeout(() => {resolve(1000);}, 1000);});const b = new Promise((resolve) => {setTimeout(() => {resolve(2000);}, 2000);});const self = "自定义all";Promise.myAll([b, a]).then((res) => {console.log(res, self);});Promise.all([b, a]).then((res) => {console.log(res, "js的all");});Promise.myAll("abc").then((res) => {console.log(res, self);});Promise.all("abc").then((res) => {console.log(res, "js的all");});</script></body>
</html>
我们先看一下执行结果
结果是没有问题的,那我们解释一下代码
const promises = Array.from(data);
将数据转成数组,没什么好说的,重点是循环
for (let i = 0; i < promises.length; i++) {Promise.resolve(promises[i]).then((res) => {result[i] = res;count++;if (count === data.length) {resolve(result);}}).catch((err) => {reject(err);});}
为什么 result[i] = res; count++; if (count === data.length) { resolve(result); }
这样写而不是result.push(res),且比较长度为什么不用i,
解释:其实我们看给的案例中b,a就清楚了,因为Promise.all返回的结果不是执行的顺序,而是你传入的顺序,即不论a的执行结果多快,b的执行多慢,只要传入的是[b,a],那么返回结果一定是b的结果在前面,如果使用result.push(res),那么如果b比a慢,就会使a的结果在第一项,b在第二项,因为a先执行完的;
那为什么比较长度不用i呢,我们在for循环打印一下i
for (let i = 0; i < promises.length; i++) {Promise.resolve(promises[i]).then((res) => {console.log(i);result[i] = res;count++;if (count === data.length) {resolve(result);}}).catch((err) => {reject(err);});}
发现结果i是1 0
那如果用i判断,b根本就不会在返回结果里面,所以需要使用额外数据来判断
继续完善代码
这就结束了吗?其实已经算是差不多结束了,但是有一个比较特殊的数据,对象呢,Promise.all如果参数传入的是对象结果是什么呢,那我们调用一下看下吧
发现,直接报错了,如果有了解过Symbol.iterator的小伙伴一下子就知道了,对象是没有Symbol.iterator这个方法的,而Promise.all传入的参数必须要有Symbol.iterator方法,字符串,数组 Set和Map是有这个方法的,但是对象没有,所以直接报错;
至于Symbol.iterator方法详解可以看一下我在csdn发布的文章js对象for…of循环,这里就不多做介绍了
那我们把代码完善一下
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>Document</title></head><body><script>Promise.myAll = (data) => {return new Promise((resolve, reject) => {if (data === null || !data[Symbol.iterator]) {reject("错误,无法迭代");return;}let result = [];let count = 0;const promises = Array.from(data);if (promises.length === 0) {return resolve(result); // 空迭代对象直接 resolve}for (let i = 0; i < promises.length; i++) {Promise.resolve(promises[i]).then((res) => {console.log(i);result[i] = res;count++;if (count === data.length) {resolve(result);}}).catch((err) => {reject(err);});}});};const a = new Promise((resolve) => {setTimeout(() => {resolve(1000);}, 1000);});const b = new Promise((resolve) => {setTimeout(() => {resolve(2000);}, 2000);});const self = "自定义all";Promise.myAll([b, a]).then((res) => {console.log(res, self);});Promise.all([b, a]).then((res) => {console.log(res, "js的all");});Promise.myAll("abc").then((res) => {console.log(res, self);});Promise.all("abc").then((res) => {console.log(res, "js的all");});Promise.myAll({}).then((res) => {console.log(res, self);});</script></body>
</html>
Promise.all是直接抛出异常的,我们这里就不抛出异常了
结果也是正常的;
那有人就提出了,如果我在对象原型添加了Symbol.iterator这个方法是不是也会正常执行then方法呢,在Promise.all中,如果对象提供了Symbol.iterator他也是可以正常执行不抛出异常的,这个就留个大家自己去实现一下把
总结
以上就是实现Promise.all的一些代码,一开始以为比较简单,但其实真正写起来还是有很多需要注意点的,主要是for循环那一段逻辑,很容易出问题
彩蛋:
我自己写完之后给远佬看过,来自远佬的高度评价,哈哈
至于他说的问题其实就是当时for循环添加结果用的是push方法
tips: 远佬可是vue的官方团队成员