手写Promise.all

发布于:2025-06-08 ⋅ 阅读:(19) ⋅ 点赞:(0)

前言

之前在看远方os大佬直播的时候看到有让手写的Promise.all的问题,然后心血来潮自己准备手写一个

开始

首先,我们需要明确原本js提供的Promise.all的特性

  1. Promise.all返回的是一个Promise
  2. 如果传入的数据中有一个reject即整个all返回的就是reject,且是第一个reject的数据
  3. 如果传入的不是Promise,all会自动用Promise.reslove将其解析成Promise完成状态(重要)
  4. all的函数不一定非要是数组
    那我们用Promise验证一下吧
    image
    image
    image
    以上就是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>

我们先看一下执行结果
image
结果是没有问题的,那我们解释一下代码
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
image
那如果用i判断,b根本就不会在返回结果里面,所以需要使用额外数据来判断

继续完善代码

这就结束了吗?其实已经算是差不多结束了,但是有一个比较特殊的数据,对象呢,Promise.all如果参数传入的是对象结果是什么呢,那我们调用一下看下吧
image
发现,直接报错了,如果有了解过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是直接抛出异常的,我们这里就不抛出异常了
image
结果也是正常的;
那有人就提出了,如果我在对象原型添加了Symbol.iterator这个方法是不是也会正常执行then方法呢,在Promise.all中,如果对象提供了Symbol.iterator他也是可以正常执行不抛出异常的,这个就留个大家自己去实现一下把

总结

以上就是实现Promise.all的一些代码,一开始以为比较简单,但其实真正写起来还是有很多需要注意点的,主要是for循环那一段逻辑,很容易出问题
彩蛋:
image
我自己写完之后给远佬看过,来自远佬的高度评价,哈哈
至于他说的问题其实就是当时for循环添加结果用的是push方法
tips: 远佬可是vue的官方团队成员


网站公告

今日签到

点亮在社区的每一天
去签到