Nodejs 配列キューと forEach アプリケーションの詳細な説明

Nodejs 配列キューと forEach アプリケーションの詳細な説明

この記事では、Nodejs 開発プロセスで遭遇する配列の特性によって発生する問題と解決策、および配列の柔軟な適用について主に記録します。

この記事のテスト結果はノードv6.9.5に基づいています。

配列とキュー

配列オブジェクトの push/shift メソッドを使用して、キューの先入れ先出し機能を実装できます。次に例を示します。

>a=[]
[]
>a.push(2.3.4)
3
>a.プッシュ(2)
3
>あ
[2.3.4.2]
>a.シフト()
2
>あ
>[3.4.2]

配列と forEach

配列を削除する一般的な方法は、delete と splice メソッドを使用する 2 つあります。これらの違いを明確にする必要があります。

操作/方法例示する
スプライス指定された配列要素を削除して返します。配列自体の長さは変わりますが、要素オブジェクトは解放されません。
消去要素オブジェクトを削除(解放)すると、配列要素は変更されず、値は未定義になります。

配列から要素を完全に削除したい場合は、splice を使用します。

> a=[1,2,3]
[ 1, 2, 3 ]
> a.スプライス(1,1)
[ 2 ]
> 1つの
[ 1, 3 ]
> a.長さ
2
> a.forEach(function(item, index){console.info("index[", index,"]:", item)});
インデックス[0]:1
インデックス[ 1 ]: 3
未定義
>

では、delete を使用して要素オブジェクトを削除した後に forEach を実行するとどのような効果があるのでしょうか?

空要素を含む配列に対する forEach の処理メカニズム

テスト結果は次のとおりです

> a=[1,2,3]
[ 1, 2, 3 ]
> [1]を削除
真実
> 1つの
[ 1, 3 ]
> a.長さ
3
> a.forEach(function(item, index){console.info("index[", index,"]:", item)});
インデックス[0]:1
インデックス[ 2 ]: 3
未定義

テスト結果から、forEach は値が未定義の項目を走査しないことがわかります。実際のアプリケーションでは、forEach が終了したかどうかを判断することが大きな課題となります。

forEach の非同期機能の適用を解決するには、配列にプロトタイプを追加して、有効なデータを自分で管理および設定します。

効果は以下のとおりです。

> a=[1,2,3]
[ 1, 2, 3 ]
> a.有効数=3
3
> 削除[2]
真実
> a.有効数=2
2
> 1つの
[ 1, 2, , 有効数字: 2 ]
> a.長さ
3
> 有効数字
2
> a.forEach(function(item, index){console.info("index[", index,"]:", item)});
インデックス[0]:1
インデックス[ 1 ]: 2
未定義
>

補足: Node.jsの配列forEachはコンテキストステートメントを同期的に処理します

私は C 言語の考え方に慣れており、初めて Node.js に触れたときは、その非同期処理に頭を悩ませました。

コードを記述するときに、配列内の要素をループで処理し、すべての処理が完了した後に最後の操作を実行する必要があるシナリオに遭遇することがあります。ただし、JS の非同期の性質により、この最後のステートメントが最初に実行されるため、forEach について学習する時間を取ってください。

口先だけではダメ。コードを見せてください。

forEach の使用法

forEach は配列構造をトラバースするために使用されます。誰かが forEach は最下層で for を使用して実装されていると言っていました。詳しくは調べていませんが、少なくとも効果は同じであるようです。 forEach のコールバック関数の 3 つのパラメーターは、値、シーケンス番号、元の配列です。シーケンス番号は0から始まります。

(() => {
  arr = [2, 3, 1]とします。
  arr.forEach(関数 (値, インデックス, 配列) {
    console.log(値);
    コンソールログ(インデックス);
    console.log(配列);
    コンソールログ('-----');
  });
})();

出力

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

結果から、forEach の複数のループが同期されている、つまり順番に実行されていることがわかります。しかし、JS であることを考えると、同期は不可能な気がします。 。確認できます。

forEachは複数のループを非同期的に処理します

今回は、forEach にタイマー タスクを追加し、各ループ操作を値に関連する時間だけ遅延させて、より時間のかかる操作をシミュレートします。

(() => {
  arr = [2, 3, 1]とします。
  arr.forEach(関数 (値, インデックス, 配列) {
    setTimeout(関数() {
      console.log(値);
    }, 値*100);
  });
})();

出力

1
2
3

結果から、最も時間の短いタスクが最初に完了し、各ループのタスクがループの順序どおりに実行されず、つまり複数のループが非同期で処理されていることがわかります。

forEachコンテキストも非同期に実行される

冒頭で述べた問題に戻ると、複数のループが順番に実行されるかどうかに関係なく、forEach 内のすべてのタスクが完了した後にデータの一部を実行して、すべてのタスクが完了したことを通知する必要があります。

(() => {
  arr = [2, 3, 1]とします。
  arr.forEach(関数 (値, インデックス, 配列) {
    setTimeout(関数() {
      console.log(値);
    }, 値*100);
  });
  console.log('すべての作業が完了しました');
})();

出力

すべての作業は完了しました
1
2
3

結果から、コンテキスト ステートメントも同期されていないことがわかります。forEach ループ内のタスクは、すべてのタスクが完了したことを通知する前に完了しません。これは明らかに期待どおりではありません。

この問題について多くのブログを読みましたが、適切な解決策を見つけることができませんでした。最終的に、Promise.all を使用してこの機能をかろうじて実装することしかできませんでした。

Promise.allはforEachコンテキストステートメントの同期処理を実装します。

上記のコードを Promise.all 構造に変更します。各ループの最後に、resolve() が呼び出されます。Promise.all の then 関数は、すべての Promise が実行されたときにのみトリガーされることがわかっており、これはニーズを満たしているようです。

(() => {
  arr = [2, 3, 1]とします。
  proArr = [] とします。
  arr.forEach(関数(値、インデックス) {
    proArr[インデックス] = 新しいPromise(関数(解決) {
      setTimeout(関数() {
        console.log(値);
        解決する();
      }, 値*100);
    });
  });
  Promise.all(proArr).then(()=>{
    console.log('すべての作業が完了しました');
  })
})();

出力

1
2
3
すべての作業は完了しました

結果から判断すると、私たちのニーズは満たされました。

起こりうる問題

JS の非同期特性を考えてみると、この方法には問題があるかもしれないと突然気づきました。

ここでは、forEach に入るたびに Promise 配列が割り当てられます。この操作時間は非常に短いはずです。最後の Promise.all ステートメントは、3 つのループで割り当てが完了した後にのみ呼び出されます。

ただし、配列が非常に大きく、ループ割り当て操作に非常に時間がかかる場合、割り当て操作の半分しか完了していないと、最後の Promise.all の実行時に渡される Promise 配列は、すべての Promise を含む配列ではない可能性があります。

この場合、Promise.all は半分の操作だけを待機します。Promise.all が待機しているとき、配列の後ろに割り当てられた Promise が待機されるかどうかは不明です。

私は JS を使い始めたばかりで、実装の仕組みを理解していないため、この問題が存在するかどうかを確認するために実験することしかできません。次に、この配列を大きくしてみましょう。配列を大きくするために最も確実な方法を使用していることをお許しください。

(() => {
  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);
  proArr = [] とします。
  arr.forEach(関数(値、インデックス) {
    proArr[インデックス] = 新しいPromise(関数(解決) {
      setTimeout(関数() {
        console.log(値);
        解決する();
      }, 値*100);
    });
  });
  Promise.all(proArr).then(()=>{
    console.log('すべての作業が完了しました');
    console.log(arr.length);
  }).catch(関数(エラー) {
    コンソールログ(エラー);
  })
})();

私のコンピューターでテストしたところ、配列の長さが 2^18 * 10 の場合、Promise はエラー RangeError: Too many elements provided to Promise.all を報告します。

配列の長さが 2^17 * 10、つまり 2621440 の場合、正常に実行されます。何度かテストした結果、最後に実行されたコマンド出力「All the work is done」が常に最後に出力されます (ターミナル バッファーが小さすぎるため、出力結果は node xx.js > log.txt リダイレクトを使用して表示用にファイルにリダイレクトされます)。

もちろん、アプリケーションにはそれほど大きな配列はありません。結果から判断すると、実際のアプリケーションでは上記の問題は存在しません。

つまり、Promise.all は forEach コンテキスト ステートメントの同期処理を実装するために使用できます。

上記は私の個人的な経験です。参考になれば幸いです。また、123WORDPRESS.COM を応援していただければ幸いです。間違いや不備な点がありましたら、遠慮なくご指摘ください。

以下もご興味があるかもしれません:
  • Node.js http モジュールの使用
  • Nodejs 探索: シングルスレッドの高並行性の原理を深く理解する
  • Node.jsを理解するのはとても簡単です
  • node.js グローバル変数の具体的な使用法
  • Node8 における AsyncHooks 非同期ライフサイクル
  • Nodejs エラー処理プロセス記録
  • Expressを使用してプロジェクトを自動的にビルドするNode.jsのプロセス全体
  • ノードでシェルスクリプトを使用する方法
  • Node.js の TCP 接続処理のコア プロセス
  • Node.jsとDenoの比較

<<:  MySQLトリガーの詳細な説明と簡単な例

>>:  Ansible を使用した Nginx のバッチ デプロイのサンプル コード

推薦する

Linux での Firewalld の高度な設定の使用に関する詳細な説明

IPマスカレードとポート転送Firewalldは2種類のネットワークアドレス変換をサポートしています...

Linux ネットワークプログラミングにおけるソケットオプションの実装

ソケットオプション機能機能: ソケットファイル記述子の属性の読み取りと設定に使用されるメソッド #i...

Dockerはポートを介してコンテナに接続します

Dockerコンテナ接続1. ネットワークポートマッピングPythonアプリケーション用のコンテナを...

英語: リンクタグはIEでhrefを自動的に補完します

英語: IE では、リンク タグによって href が自動的に補完されます。 Ajax Link T...

Nginx で 403 forbidden を解決するための完全な手順

ウェブページに403 Forbiddenと表示されるNginx (yum インストール ログは通常 ...

CentOS 7 で rpm パッケージを使用して MySQL 5.7.18 をインストールする

最近、MySQL を使っています。Linux での mysql-installation という記事...

Vueのドラッグスクリーンショット機能を実装する簡単な方法

マウスをドラッグしてページのスクリーンショットを撮ります(指定した領域にスクリーンショットをドラッグ...

MySQL サービス 1067 エラーの解決策: mysql 実行可能ファイルのパスを変更する

今日、MySQLサービス1067エラー問題に遭遇しました。システムアカウントを使用するように設定して...

nginx を使用してカナリアリリースをシミュレートする方法

この記事では、ブルーグリーン デプロイメントと、nginx を使用してカナリア リリースを最も簡単な...

一般的な Dockerfile コマンドの使用方法の紹介

目次01 CM 02 エントリーポイント03 ワークディレクトリ04 環境05 ユーザー06巻07 ...

Linux mysql5.5 を mysql5.7 にアップグレードする手順と落とし穴

目次Linux MySQL 5.5 が MySQL 5.7 にアップグレードされました1. mysq...

Vue+element ui はアンカーの配置を実現します

この記事では、アンカー配置を実現するためのVue +要素UIの具体的なコードを例として紹介します。具...

PXEを使用してLinuxシステムを自動的に展開する方法

目次背景DHCPの設定DHCP ファイル (動的ホスト構成プロトコル) の編集tftp 設定sysl...

MySQL パフォーマンス チューニングについて知っておくべき 15 個の重要な変数 (要約)

序文: MYSQL は最も人気のある WEB バックエンド データベースです。最近、NOSQL がま...

InnoDB がシリアル化分離レベルを実装する方法

シリアル化の実装InnoDB は 2 つの方法でシリアル化を実装します。まず、SELECT 文が明示...