Nodejs 数组的队列以及forEach的应用详解

作者:WificamSDK7 时间:2024-05-02 16:18:39 

本文主要记录了在Nodejs开发过程中遇到过的由数组特性引起的问题及解决方式,以及对数组的灵活应用。

本文代码测试结果均基于node v6.9.5

数组与队列

利用数组对象方法push/shift可实现队列先进先出特性,例如:


>a=[]
[]
>a.push(2.3.4)
3
>a.push(2)
3
>a
[2.3.4.2]
>a.shift()
2
>a
>[3.4.2]

数组与forEach

对数组的删除操作有两种常见方式:delete和使用splice方法,需要明确他们的区别。

操作/方法说明
splice删除并返回指定的数组元素,数组本身长度会改变;但不会free元素对象
delete删除(free)元素对象,数组元素不变,值变为undefined

如果要从数组中彻底删除某个元素,使用splice即可:


> a=[1,2,3]
[ 1, 2, 3 ]
> a.splice(1,1)
[ 2 ]
> a
[ 1, 3 ]
> a.length
2
> a.forEach(function(item, index){console.info("index[", index,"]:", item)});
index[ 0 ]: 1
index[ 1 ]: 3
undefined
>

那么,当使用delete删除某个元素对象后,此时执行forEach的效果是什么?

forEach对含空元素数组处理机制

测试结果如下


> a=[1,2,3]
[ 1, 2, 3 ]
> delete a[1]
true
> a
[ 1, , 3 ]
> a.length
3
> a.forEach(function(item, index){console.info("index[", index,"]:", item)});
index[ 0 ]: 1
index[ 2 ]: 3
undefined

从测试结果来看,forEach并不会遍历到值为undefined的哪一项。这在实际应用中如何判断forEach是否结束是一大挑战。

解决配合forEach的异步特性应用,可为数组添加prototype来自行管理设置有效数据;

效果如下:


> a=[1,2,3]
[ 1, 2, 3 ]
> a.validnum=3
3
> delete a[2]
true
> a.validnum=2
2
> a
[ 1, 2, , validnum: 2 ]
> a.length
3
> a.validnum
2
> a.forEach(function(item, index){console.info("index[", index,"]:", item)});
index[ 0 ]: 1
index[ 1 ]: 2
undefined
>

补充:Node.js 数组 forEach 同步处理上下文语句

习惯了C语言系的思维方式,刚接触Node.js,它的异步处理让我头大。

写代码遇到这么一个场景,需要循环对一个数组中的元素进行处理,全部处理完成后再执行一个last操作。但是JS的异步特性会使这个last语句先执行,所以花点时间研究研究forEach。

Talk is cheap. Show me the code.

forEach 用法

forEach用于对数组结构进行遍历,看到有人说forEach底层是用for实现的,没深究,起码效果上看是一样的。forEach的回调函数3个参数分别是:值、序号和原数组。序号从0开始。


(() => {
 let arr = [2, 3, 1];
 arr.forEach(function (value, index, array) {
   console.log(value);
   console.log(index);
   console.log(array);
   console.log('-----');
 });
})();

Output


2
0
[ 2, 3, 1 ]
-----
3
1
[ 2, 3, 1 ]
-----
1
2
[ 2, 3, 1 ]
-----

从结果上看forEach多次循环之间是同步的,也就是说都是按顺序执行的。但是一想到它是JS就感觉不可能同步的。。可以验证一下。

forEach 异步处理多次循环

这次在forEach加个定时任务,每次循环操作都延时value相关的时间,模拟比较耗时的操作。


(() => {
 let arr = [2, 3, 1];
 arr.forEach(function (value, index, array) {
   setTimeout(function () {
     console.log(value);
   }, value*100);
 });
})();

Output


1
2
3

从结果可以看出耗时最短的任务先完成,每次循环的任务并不是按循环的先后顺序执行的,也就是说异步处理多次循环。

forEach 上下文也是异步执行

回到开始说到的问题了,且不管多次循环是不是按顺序执行,我需要forEach中的所有任务都完成后执行一条数据来通知我任务全部完成了。


(() => {
 let arr = [2, 3, 1];
 arr.forEach(function (value, index, array) {
   setTimeout(function () {
     console.log(value);
   }, value*100);
 });
 console.log('All the work is done');
})();

Output


All the work is done
1
2
3

从结果来看,上下文的语句也不是同步的,forEach循环中的任务没有完成就通知所有任务都完成了,显然不符合预期。

针对这个问题看了好多个博客,都没有找到合适的解决方法,最后只能想到用Promise.all来勉强实现这个功能。

Promise.all 实现 forEach 上下文语句同步处理

把上面的代码改成Promise.all的结构。每个循环中执行结束调用resolve(),我们知道Promise.all的then函数,只有所有的Promise都执行完成才会触发,这样好像能满足我们的需求。


(() => {
 let arr = [2, 3, 1];
 let proArr = [];
 arr.forEach(function (value, index) {
   proArr[index] = new Promise(function (resolve) {
     setTimeout(function () {
       console.log(value);
       resolve();
     }, value*100);
   });
 });
 Promise.all(proArr).then(()=>{
   console.log('All the work is done');
 })
})();

Output


1
2
3
All the work is done

从结果来看,满足了我们的需求。

可能还存在的问题

想到JS异步特性,突然发现可能这个方法还存在个问题。

这里每次 forEach 刚进入就对 Promise 数组进行了赋值操作,这个操作时间应该非常短,循环3次都赋值完成后才调用最后的Promise.all语句。

但是如果这个数组非常大,这个循环赋值的操作非常耗时间的话,假如只完成了一半的赋值操作,那么执行最后这个 Promise.all 的时候传入的 Promise 数组可能并不是包含所有 Promise 的数组。

这样的话 Promise.all 等待的就只有一半的操作,Promise.all 等待的时候,这个数组后面被赋值的 Promise 不知道会不会被等待。

刚接触JS不明白实现机制,只能实验来验证一下是否存在这个问题。接下来用把这个数组弄大一些,请原谅我用最傻瓜式的方式搞大它。


(() => {
 let arr = [2, 3, 1, 2, 3, 1, 2, 3, 1, 2];  // 10
 arr= arr.concat(arr);  // 2^1 * 10
 arr= arr.concat(arr);  // 2^2 * 10
 arr= arr.concat(arr);  // 2^3
 arr= arr.concat(arr);  // 2^4
 arr= arr.concat(arr);  // 2^5
 arr= arr.concat(arr);
 arr= arr.concat(arr);
 arr= arr.concat(arr);
 arr= arr.concat(arr);
 arr= arr.concat(arr);  // 2^10
 arr= arr.concat(arr);
 arr= arr.concat(arr);
 arr= arr.concat(arr);
 arr= arr.concat(arr);
 arr= arr.concat(arr);  // 2^15
 arr= arr.concat(arr);
 arr= arr.concat(arr); // 2^17 * 10
// arr= arr.concat(arr);  // 2^18 * 10
 console.log(arr.length);
 let proArr = [];
 arr.forEach(function (value, index) {
   proArr[index] = new Promise(function (resolve) {
     setTimeout(function () {
       console.log(value);
       resolve();
     }, value*100);
   });
 });
 Promise.all(proArr).then(()=>{
   console.log('All the work is done');
   console.log(arr.length);
 }).catch(function (err) {
   console.log(err);
 })
})();

经过测试在我这个电脑上当数组长度为2^18 * 10的时候,Promise报错 RangeError: Too many elements passed to Promise.all。

当数组长度为2^17 * 10 即2621440的时候,会正常运行。测试了几次,最后的执行命令输出的All the work is done始终在最后输出(因为终端缓冲区太小,所以使用node xx.js > log.txt重定向的方式把输出结果重定向到文件查看)。

当然应用中也不会有这么大的数组,从结果看的话,就是实际应用中不存在上面考虑可能出现的问题。

也就是说可以用 Promise.all 实现 forEach 上下文语句同步处理。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持asp之家。如有错误或未考虑完全的地方,望不吝赐教。

来源:https://blog.csdn.net/WificamSDK7/article/details/78460408

标签:Nodejs,数组,队列,forEach
0
投稿

猜你喜欢

  • 利用pyinstaller或virtualenv将python程序打包详解

    2022-08-01 10:37:52
  • python自动化测试selenium屏幕截图示例

    2022-07-13 16:55:14
  • IE下文本被折行的解决方案

    2009-06-28 16:13:00
  • MySQL学习第四天 Windows 64位系统下使用MySQL

    2024-01-24 02:35:46
  • PyQt5 PySide2 触摸测试功能的实现代码

    2022-06-23 22:22:25
  • 修改python plot折线图的坐标轴刻度方法

    2021-06-24 09:09:43
  • Dreamweaver小技巧:超高速下载图像

    2009-07-14 21:59:00
  • SQLServer中bigint转int带符号时报错问题解决方法

    2024-01-16 00:13:01
  • Python简单读写Xls格式文档的方法示例

    2021-11-02 13:27:30
  • Python+OpenCV之形态学操作详解

    2021-11-24 23:37:58
  • Python地图四色原理的遗传算法着色实现

    2022-10-07 20:57:45
  • Python 实现Mac 屏幕截图详解

    2021-06-16 13:43:52
  • Security安装 Elastic SIEM 和 EDR的超详细教程

    2022-03-08 19:55:43
  • MySQL新手入门指南--快速参考

    2024-01-21 09:58:14
  • Python 反转字符串(reverse)的方法小结

    2023-05-28 11:04:22
  • Python如何批量生成和调用变量

    2023-11-27 21:21:08
  • 浅谈Python从全局与局部变量到装饰器的相关知识

    2022-06-02 12:23:26
  • 用go gin server来做文件上传服务

    2024-05-29 22:09:12
  • Pandas实现DataFrame的简单运算、统计与排序

    2022-03-29 02:09:54
  • centos7 PHP环境搭建 GD库 等插件安装方法

    2023-11-05 21:25:38
  • asp之家 网络编程 m.aspxhome.com