为什么for和forEach循环一个异步函数效果会不同

前言

为什么for和forEach循环一个异步函数效果会不同?

正文

forforEach在使用async、await循环一个异步函数并不是说结果不同,而是效果不同,准确来说应该是得到结果的时机不相同,会有一丝丝差异,为什么会出现这种情况呢?

看例子:

js 复制代码
//定义一个数组数据
 const arr = ["苹果", "香蕉", "橘子", "梨", "葡萄", "西瓜", "草莓", "火龙果", "车厘子"];
 //定义一个导出promise的异步函数,异步时间是一秒钟
   const p = (v, c) =>
     new Promise((resolve, reject) => {
       setTimeout(() => {
         console.log(v, c);
         resolve();
       }, 1000);
     });

下面分别使用forforeach遍历这个异步函数

js 复制代码
//foreach接收一个回调函数,并使用async和await关键字让遍历的函数'同步执行'
   arr.forEach(async (element) => {
      await p(element, "foreach");
    });
    //同foreach一样的操作使用for循环
    const inits = async () => {
      for (let i = 0; i < arr.length; i++) {
        await p(arr[i], "for");
      }
    };
    inits();

上面两个循环按我们正常的思路第一时间想到的应该就是数组中的数据会依次打印出来,并且每一个数据打印两遍,但是实际的结果如我们想象的一样吗?

可以看出,使用foreach循环的异步函数虽然使用async、await关键字,但依旧是在一秒后数组中的数据全部加载了出来。而for循环的异步函数确实是我们想象中的那种,在使用了async、await关键字之后,每隔一秒才会显示数组中的下一个元素。

是什么导致了foreach循环异步函数的时候超出我们的预期了呢?

首先要看一下foreach的实现,这里大致写一下foreach的实现代码

js 复制代码
    //类似 foreach的源码实现   其实还是很简单的
    Array.prototype.myForEach = function (callback) {
    //判断确实传进来的一个函数
       if (typeof callback !== 'function') {  
        throw new Error('Callback must be a function');  
       } 
       //判断数组长度,如果为0直接return
      if (this.length === 0) return; 
      for (let i = 0; i < this.length; i++) {
      //在这里,callback函数是在数组的每个元素上调用的,所以需要将当前元素作为this参数传递给callback函数
        callback.call(this[i],this[i], i, this);
      }
    };
    
  //调用一下看看是否正确
 arr.myForEach((element, index, arr) => {
    console.log(element, index, arr);
  });

从最终打印的结果来看foreach的实现是正确的。

看完foreach源码之后是不是就可以理解为,foreach循环就是for循环的时候又多了一层函数掉调用。

理念有了,开始尝试

js 复制代码
//可以理解为在这个函数调用的异步函数加了await 他这里'同步执行'了
const each = async (v) => {
        await p(v, "for");
      };
 //但是在for循环的时候并没有加await 在for循环的时候直接将这个没有进行'同步执行'的函数一次经循环完了
 //中间不会有阻塞,导致最终打印的显示时机与直接在for循环内await 一个异步函数不同
const inits = async () => {
   for (let i = 0; i < arr.length; i++) {
     each(arr[i]);
   }
 };
 inits();

这样多加了一层没有'阻塞'函数之后,最终的效果和foreach展示的是一样的。

如果想要foreach循环同for循环一样循环一个异步函数,每隔一定的时间才会显示下一个,也可以自己写一个'同步阻塞'的foreach

js 复制代码
//重写一个同步foreach 在循环的时候加上await
Array.prototype.syncForEach = async function (callback) {
        if (typeof callback !== "function") return;
        if (this.length === 0) return;
        for (let i = 0; i < this.length; i++) {
          await callback.call(this[i], this[i], i, this);
        }
      };
      
      //当这里调用的时候如果是个异步函数,syncForEach里的await也会生效,进行阻塞同步执行
      arr.syncForEach(async (element) => {
        await p(element, "foreach");
      });

最终得到的结果也是和我们预想用的一样了。

最后

其实forforeach的区别在于中间多了一个函数调用,并且这个函数是普通函数,导致在遍历异步函数的时候会先将这个函数全部调用,在函数内才使用async、await进行阻塞函数执行,这个时候函数已经多次调用,所以最终结果会一次性全部显示出来。

使用回调函数、Promiseasyncawait可以产生异步传染性 的效果,只有当这个异步执行完毕之后才会执行下一个异步函数,所以在自定义一个阻塞foreach的时候添加上async、await就达到了与直接使用for遍历一个异步函数的效果。

相关推荐
天蓝色的鱼鱼1 小时前
前端开发者的组件设计之痛:为什么我的组件总是难以维护?
前端·react.js
codingandsleeping1 小时前
使用orval自动拉取swagger文档并生成ts接口
前端·javascript
石金龙2 小时前
[译] Composition in CSS
前端·css
白水清风2 小时前
微前端学习记录(qiankun、wujie、micro-app)
前端·javascript·前端工程化
Ticnix2 小时前
函数封装实现Echarts多表渲染/叠加渲染
前端·echarts
用户22152044278002 小时前
new、原型和原型链浅析
前端·javascript
阿星做前端2 小时前
coze源码解读: space develop 页面
前端·javascript
叫我小窝吧2 小时前
Promise 的使用
前端·javascript
NBtab3 小时前
Vite + Vue3项目版本更新检查与页面自动刷新方案
前端
天天扭码3 小时前
来全面地review一下Flex布局(面试可用)
前端·css·面试