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


Posted in NodeJs onFebruary 25, 2021

本文主要记录了在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 上下文语句同步处理。

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

NodeJs 相关文章推荐
在windows上用nodejs搭建静态文件服务器的简单方法
Aug 11 NodeJs
NodeJs的优势和适合开发的程序
Aug 14 NodeJs
async/await与promise(nodejs中的异步操作问题)
Mar 03 NodeJs
nodejs根据ip数组在百度地图中进行定位
Mar 06 NodeJs
mac下的nodejs环境安装的步骤
May 24 NodeJs
Nodejs实现爬虫抓取数据实例解析
Jul 05 NodeJs
NodeJS模块与ES6模块系统语法及注意点详解
Jan 04 NodeJs
NodeJS读取分析Nginx错误日志的方法
May 14 NodeJs
nodejs实现获取本地文件夹下图片信息功能示例
Jun 22 NodeJs
图解NodeJS实现登录注册功能
Sep 16 NodeJs
NodeJS http模块用法示例【创建web服务器/客户端】
Nov 05 NodeJs
nodejs中的异步编程知识点详解
Jan 17 NodeJs
一文秒懂nodejs中的异步编程
Jan 28 #NodeJs
在nodejs中创建child process的方法
Jan 26 #NodeJs
nodejs中使用worker_threads来创建新的线程的方法
Jan 22 #NodeJs
Nodejs 微信小程序消息推送的实现
Jan 20 #NodeJs
Nodejs实现微信分账的示例代码
Jan 19 #NodeJs
nodejs中的异步编程知识点详解
Jan 17 #NodeJs
nodejs+express最简易的连接数据库的方法
Dec 23 #NodeJs
You might like
.htaccess文件保护实例讲解
2011/02/06 PHP
php使用curl发送json格式数据实例
2013/12/17 PHP
php返回字符串中所有单词的方法
2015/03/09 PHP
php实现判断访问来路是否为搜索引擎机器人的方法
2015/04/15 PHP
PHP简单获取网站百度搜索和搜狗搜索收录量的方法
2016/08/23 PHP
PHP模拟http请求的方法详解
2016/11/09 PHP
获取当前网页document.url location.href区别总结
2008/05/10 Javascript
jquery批量设置属性readonly和disabled的方法
2014/01/24 Javascript
javascript制作幻灯片(360度全景图片)
2015/07/28 Javascript
javascript实现选中复选框后相关输入框变灰不可用的方法
2015/08/11 Javascript
jquery 遍历数组 each 方法详解
2016/05/25 Javascript
VUEJS实战之构建基础并渲染出列表(1)
2016/06/13 Javascript
AngularJS 实现JavaScript 动画效果详解
2016/09/08 Javascript
JavaScript面向对象精要(下部)
2017/09/12 Javascript
Vue中多个元素、组件的过渡及列表过渡的方法示例
2019/02/13 Javascript
ES6知识点整理之数组解构和字符串解构的应用示例
2019/04/17 Javascript
python解析发往本机的数据包示例 (解析数据包)
2014/01/16 Python
Python UnicodeEncodeError: 'gbk' codec can't encode character 解决方法
2015/04/24 Python
Python os模块学习笔记
2015/06/21 Python
Python中取整的几种方法小结
2017/01/06 Python
完美解决在oj中Python的循环输入问题
2018/06/25 Python
python3.4控制用户输入与输出的方法
2018/10/17 Python
Python3.6简单的操作Mysql数据库的三个实例
2018/10/17 Python
解决Pycharm 导入其他文件夹源码的2种方法
2020/02/12 Python
维氏瑞士军刀英国网站:Victorinox英国
2019/07/04 全球购物
毕业生自荐信的主要内容
2013/10/29 职场文书
餐饮业创业计划书范文
2014/01/06 职场文书
料理师求职信
2014/01/30 职场文书
水利公司纪检监察自我鉴定
2014/02/25 职场文书
党建工作先进材料
2014/05/02 职场文书
小学生倡议书范文
2014/05/13 职场文书
党在我心中演讲稿
2014/09/02 职场文书
2014年财务工作总结范文
2014/11/11 职场文书
2014最新股权信托合同协议书
2014/11/18 职场文书
纯CSS实现hover图片pop-out弹出效果的实例代码
2021/04/16 HTML / CSS
windows server2008 开启端口的实现方法
2022/06/25 Servers