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

相关推荐
J不A秃V头A23 分钟前
Vue3:编写一个插件(进阶)
前端·vue.js
司篂篂1 小时前
axios二次封装
前端·javascript·vue.js
姚*鸿的博客1 小时前
pinia在vue3中的使用
前端·javascript·vue.js
宇文仲竹2 小时前
edge 插件 iframe 读取
前端·edge
Kika写代码2 小时前
【基于轻量型架构的WEB开发】【章节作业】
前端·oracle·架构
天下无贼!3 小时前
2024年最新版Vue3学习笔记
前端·vue.js·笔记·学习·vue
Jiaberrr3 小时前
JS实现树形结构数据中特定节点及其子节点显示属性设置的技巧(可用于树形节点过滤筛选)
前端·javascript·tree·树形·过滤筛选
赵啸林3 小时前
npm发布插件超级简单版
前端·npm·node.js
我码玄黄3 小时前
THREE.js:网页上的3D世界构建者
开发语言·javascript·3d
罔闻_spider3 小时前
爬虫----webpack
前端·爬虫·webpack