为什么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遍历一个异步函数的效果。

相关推荐
尘中客3 小时前
放弃 Echarts?前端直接渲染后端高精度 SVG 矢量图流的踩坑记录
前端·javascript·echarts·前端开发·svg矢量图·echarts避坑
FreeBuf_3 小时前
Chrome 0Day漏洞遭野外利用
前端·chrome
小彭努力中3 小时前
199.Vue3 + OpenLayers 实现:点击 / 拖动地图播放音频
前端·vue.js·音视频·openlayers·animate
2501_916007474 小时前
网站爬虫原理,基于浏览器点击行为还原可接口请求
前端·javascript·爬虫·ios·小程序·uni-app·iphone
前端大波4 小时前
Sentry 每日错误巡检自动化:设计思路与上手实战
前端·自动化·sentry
Highcharts.js5 小时前
适合报表系统的可视化图表|Highcharts支持直接导出PNG和PDF
javascript·数据库·react.js·pdf
ZC跨境爬虫5 小时前
使用Claude Code开发校园交友平台前端UI全记录(含架构、坑点、登录逻辑及算法)
前端·ui·架构
慧一居士5 小时前
Vue项目中,何时使用布局、子组件嵌套、插槽 对应的使用场景,和完整的使用示例
前端·vue.js
叫我一声阿雷吧5 小时前
JS 入门通关手册(35):执行上下文、调用栈与作用域链深度解析
javascript·作用域链·js进阶·执行上下文·调用栈·变量提升·闭包原理
Можно5 小时前
uni.request 和 axios 的区别?前端请求库全面对比
前端·uni-app