之前遇到过一些人跟我说,forEeach 是异步的~不能放异步代码进去执行~我当时其实挺迷惑的,好像当初学习 javascript 的时候不是这样的

<script>const arr = [1, 2, 3, 4]console.log('start')arr.forEach(item => {console.log(item)})console.log('end')
</script>


请不要在说 foreach 是一个异步的了!

forEach 执行异步代码
可能你遇到的情况是 forEach 中执行的都是异步函数,你想在里面逐个执行出来!但是不行!比如下面的代码

const sleep = (ms) => {return new Promise((resolve) => {setTimeout(resolve, ms)})
}
const arr = [() => console.log("start"),() => sleep(1000),() => console.log(1),() => sleep(1000),() => console.log(2),() => sleep(1000),() => console.log(3),() => sleep(1000),() => console.log("end")
]
arr.forEach(async fn => {await fn()
})
console.log("for循环执行完")
这里 await 的 ‘跳出当前线程的操作,因为在for循环里,
所以每次跳出也是跳出当前这次循环,但并没有跳到for循环外面。

期待的结果是先打印 start, 然后每隔一秒往下执行一次,直到 end,但是你执行会发现,这些 console 会一次瞬间执行完成!

那为什么会这样呢?我们可以去 mdn 找到 forEach 源码

// Production steps of ECMA-262, Edition 5, 15.4.4.18
// Reference: http://es5.github.io/#x15.4.4.18
if (!Array.prototype.forEach) {Array.prototype.forEach = function(callback, thisArg) {var T, k;if (this == null) {throw new TypeError(' this is null or not defined');}// 1. Let O be the result of calling toObject() passing the// |this| value as the argument.var O = Object(this);// 2. Let lenValue be the result of calling the Get() internal// method of O with the argument "length".// 3. Let len be toUint32(lenValue).var len = O.length >>> 0;// 4. If isCallable(callback) is false, throw a TypeError exception.// See: http://es5.github.com/#x9.11if (typeof callback !== "function") {throw new TypeError(callback + ' is not a function');}// 5. If thisArg was supplied, let T be thisArg; else let// T be undefined.if (arguments.length > 1) {T = thisArg;}// 6. Let k be 0k = 0;// 7. Repeat, while k < lenwhile (k < len) {var kValue;// a. Let Pk be ToString(k).//    This is implicit for LHS operands of the in operator// b. Let kPresent be the result of calling the HasProperty//    internal method of O with argument Pk.//    This step can be combined with c// c. If kPresent is true, thenif (k in O) {// i. Let kValue be the result of calling the Get internal// method of O with argument Pk.kValue = O[k];// ii. Call the Call internal method of callback with T as// the this value and argument list containing kValue, k, and O.callback.call(T, kValue, k, O);}// d. Increase k by 1.k++;}// 8. return undefined};
}

关键代码在这里

foreach 内部呢是一个 while 循环,然后再内部去执行回调函数!你可以看到并没有对内部异步进行什么处理~

async fn => {await fn()
}

相当于每次循环,都只是回调执行了外层的 fn, 执行就完事了,而没有对内部的 await 做一些操作,其实就是在循环中没有去等待执行 await 的结果,所以里面的异步 sleep, 还是放到异步队列去等待同步执行完成后再去执行,也就是先打印再去 sleep, 所以没有 sleep 的效果

如果直接用 for 循环去处理,那么就是针对每一次循环去做了一个异步的 await,

const sleep = (ms) => {return new Promise((resolve) => {setTimeout(resolve, ms)})
}
const arr = [() => console.log("start"),() => sleep(1000),() => console.log(1),() => sleep(1000),() => console.log(2),() => sleep(1000),() => console.log(3),() => sleep(1000),() => console.log("end")
]async function run(arr) {for (let i = 0; i < arr.length; i++) {await arr[i]()}
}
run(arr)(async function(){})()//或者使用匿名函数!!!

写一个自己的 forEach
其实就是把 forEach 内部实现改成 for 循环,让每次循环都能捕捉执行 await, 而不是外层的 async 函数

Array.prototype.asyncForEach = async function (callback, args) {const _arr = this, // 因为调用的方式 [1,2,3].asyncForEach this指向数组isArray = Array.isArray(_arr), //判断调用者是不是数组_args = args ? Object(args) : window //对象化if (!isArray) {throw new TypeError("the caller must be a array type!")}for (let i = 0; i < _arr.length; i++) {await callback.call(_args,_arr[i])}
}const sleep = (ms) => {return new Promise((resolve) => {setTimeout(resolve, ms)})
}
const arr = [() => console.log("start"),() => sleep(1000),() => console.log(1),() => sleep(1000),() => console.log(2),() => sleep(1000),() => console.log(3),() => sleep(1000),() => console.log("end")
]
arr.asyncForEach(async(fn)=>{await fn()
})

测试一下,成功的!




一、forEach外部等待forEach执行完成

    let arr = [1, 2, 3, 4, 5, 6, 7];let arr2 = [];arr.forEach((item) => {setTimeout(() => {arr2.push(item);}, 1000);});console.log(JSON.parse(JSON.stringify(arr2))); // []

如上:当forEach内部处理异步操作时,则forEach同时也处于异步状态,并不会阻塞进程,而是让下面的语句先执行

即如果在使用forEach遍历数组时,forEach内有异步操作,那么后面的代码执行是不会等待forEach的执行结果,但是很明显我们想要拿到的是forEach结束后的数据,此时我们只需要将forEach写到promise里,即使forEach处于同步状态。

把每次的循环都包裹到一个Promise里,形成一个Promise队列(asyncFuns),最后使用Promise.all来判断是否全部执行完毕

let arr = [1, 2, 3, 4, 5, 6, 7];
let arr2= [];
function pro(item) {return new Promise((resolve, reject) => {setTimeout(() => {arr2.push(item);resolve();}, 10000);});
}
let asyncFuns = [];
arr.forEach((item) => {asyncFuns.push(pro(item));
});
Promise.all(asyncFuns).then(() => {console.log('res', arr2); // 十秒钟后打印:res (7) [1, 2, 3, 4, 5, 6, 7]
});

或者使用原生的for循环

(async function () {for (let index = 0; index < flisdata.length; index++) {const item = flisdata[index];if (item.FLIID) {let imgdataBase64 = await that.getFileById(item.FLIID);that.loading = false;if (!imgdataBase64) {// return} else {that.$set(item, 'imgsrc', flisdata[index].FLIOSSKEY + ',' + imgdataBase64);//注意这里不是给vm的一个属性赋值,而只是利用了$set 这个函数的赋值作用。不写这个直接用 点语法也行;//Vue.set(vm.items, indexOfItem, newValue) 采用这种直接从vm.到对象 会不会就不用写下面的that.filedata = flisdata了 ??that.loadingArray[index] = false;}// 获取当前页面的路径// let pathheader = window.location.protocol;// let pathName = window.location.host;// let paths = pathheader + '//' + pathName;}that.filedata = flisdata;//写这就是为了 每次获取item的src后,立刻重新给vm的filedata 赋值,然后更新;这样就做到了每异步获取一个附件轮播图就显示一个附件的效果}
})();

二、forEach内部等待异步执行完成

let arr = [1, 2, 3, 4, 5, 6, 7];
let arr2 = [];
function pro(item) {return new Promise((resolve, reject) => {setTimeout(() => {arr2.push(item);console.log(arr2);resolve();}, 1000);});
}
arr.forEach(async (item) => {await pro(item);console.log('这里是等待每一次循环结束后的操作');
});

三、既需要forEach内部同步执行,又需要forEach外部同步执行

forEach方法用于调用数组的每个元素,并将元素传递给回调函数
map方法返回一个新数组,数组中的元素为原始数组元素调用函数处理后的值
由于forEach执行并不返回任何数据,则无法使用Promise.all方法进行循环是否结束的判断,于是我们想到了使用map+Promise.all来处理这种情况

let arr = [1, 2, 3, 4, 5, 6, 7];
let arr2 = [];
function pro(item) {return new Promise((resolve, reject) => {setTimeout(() => {arr2.push(item);console.log(arr2);resolve();}, 1000);});
}
Promise.all(arr.map((item) => {return new Promise(async (resolve, reject) => {await pro(item);console.log('这里是等待每一次循环结束后的操作');resolve();});})
).then(() => {console.log('res', arr2);
});

但是实际上forEach也可以手动设定条件来判断是否遍历结束,由于forEach遍历为顺序执行遍历,所以我们可以使用当前项的index值来判断当前项是否为该数组的最后一项,如果为最后一次遍历,那么我们让程序执行下一步的操作即可

// 使用forEach进行数组遍历处理
let arr = [1, 2, 3, 4, 5, 6, 7];
let arr2 = [];
function pro(item) {return new Promise((resolve, reject) => {setTimeout(() => {arr2.push(item);console.log(arr2);resolve();}, 1000);});
}
arr.forEach(async (item, index) => {await pro(item);console.log('这里是等待每一次循环结束后的操作');if (index === arr.length - 1) {console.log('res', arr2);}
});

forEach同/异步问题相关推荐

  1. 组员大眼瞪小眼,forEach处理异步任务遇到的坑

    背景 大家好,我是zz,时间发生在上周,一位组员遇到一个问题,几个同事都没能帮忙解决,我在这边就开门见山直接描述当时他遇到的问题.他在forEach处理了异步,但是始终不能顺序执行,至此想要的数据怎么 ...

  2. 异步方法中map、forEach和for循环中带来的异步执行问题

    关键词:map() forEach() for() 异步执行 res.jsonp() CSDN个人博客:http://blog.csdn.net/sam976 问题描述 在map循环中使用mongoo ...

  3. C#8.0宝藏好物Async streams

    之前写<.NET gRPC 核心功能初体验>,利用gRPC双向流做了一个打乒乓的Demo,存储消息的对象是IAsyncEnumerable<T>,这个异步可枚举泛型接口支撑了g ...

  4. [翻译]初试C# 8.0

    原文地址: https://blogs.msdn.microsoft.com/dotnet/2018/12/05/take-c-8-0-for-a-spin/ 初试C# 8.0 昨天我们宣布了Visu ...

  5. C# 8.0 预览特性

    初试C# 8.0 Visual Studio 2019的第一个预览版(使用Visual Studio 2019提高每个开发人员的工作效率)和.NET Core 3.0(宣布.NET Core 3预览1 ...

  6. JS循环及跳出循环总结

    前言 今天来总结一下JS中循环方法的使用,以及如何跳出循环/遍历. 正文 跳出循环有四种方式 break 用来跳出循环. continue 跳过当次循环,继续下一次的循环 return 跳出循环,并返 ...

  7. [译]初试C# 8.0

    原文地址: https://blogs.msdn.microsoft.com/dotnet/2018/12/05/take-c-8-0-for-a-spin/ 初试C# 8.0 昨天我们宣布了Visu ...

  8. jsforeach异步的问题_js中forEach回调同异步题目

    js中forEach自身是同步的 举个栗子: [many, too many, so many].forEach((value) => { some code; // 这是一个大数值运算(非异步 ...

  9. java for循环 等待_在forEach循环中使用异步/等待

    在forEach循环中使用async / await是否有任何问题? 我正在尝试遍历文件数组并await每个文件的内容. import fs from 'fs-promise' async funct ...

最新文章

  1. Yolo:实时目标检测实战(上)
  2. 安装hadoop图文
  3. python实现nginx图形界面管理
  4. 19_05_01校内训练[划分]
  5. 三个基于.net的浏览器内核使用的比较
  6. python从标准输入读取数据_在PYTHON中如何从标准输入读取内容stdin
  7. [转]【高并发】高并发秒杀系统架构解密,不是所有的秒杀都是秒杀!
  8. 《Android Studio开发实战 从零基础到App上线》源码运行问题解答
  9. c语言 不显示dos窗口,运行DOS批处理不显示DOS窗口的方法
  10. Atitit 核心代码包括哪些 重要部分 压缩 分类 图像处理部分 爬虫 分词检索部分 多媒体 基础设施代码 非功能性 类库框架 源到源的语言翻译 跨语言集成 互操作通讯 算
  11. vlan 的三种模式
  12. Drools规则引擎快速入门(一)
  13. 修改计算机ip 批处理,怎么使用批处理bat设置静态IP地址?
  14. 什么是实例?什么是引用?
  15. 系统安全检测(使用John进行密码破解)
  16. 获取最新、最全的小红书地理位置签到数据。
  17. 自定义文件格式注册和图标设置
  18. 05,JavaScript脚本中cookie
  19. 靠腰,badboy录制脚本老是发生脚本错误
  20. 某微信小程序连锁超市响应参数解密

热门文章

  1. 数据结构与算法 第四课
  2. PEP8-python代码样式指南(Style Guide for Python Code)
  3. execution()表达式
  4. 计算机二级c语言.考试题库及答案,计算机等级考试题库,二级C语言试题及答案...
  5. 广度搜索解决迷宫问题
  6. 学习笔记:Python写英语字典
  7. 第四章 确定女人的感觉
  8. UVALive 6277 - Addictive Bubbles (模拟)
  9. 直播软件app开发:如何保证音视频质量?
  10. Qt Quick 3D简介