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 のバッチ デプロイのサンプル コード

推薦する

vue3で注意すべき2つのポイントを詳しく解説:セットアップ

目次vue2の場合vue3ではセットアップに関する注意事項セットアップライフサイクルは、before...

JavaScript Sandboxについての簡単な説明

序文:サンドボックスといえば、私たちの頭には反射的に上の写真が思い浮かび、すぐに興味がわいてくるかも...

Linux で crontab 出力リダイレクトが有効にならない問題の解決方法

質問LINUX では、定期的なタスクは通常、cron デーモン プロセス [ps -ef | gre...

Yahooが開発したウェブページスコアリングプラグインYSlowのスコアリングルール

YSlow は、Yahoo USA が開発したページ スコアリング プラグインです。非常に優れていま...

MySQL 5.7 でルートパスワードを変更する方法

MySQL 5.7 以降では、多くのセキュリティ更新が追加されました。旧バージョンのユーザーは慣れて...

フロントエンド開発に必須:推奨されるブラウザ互換性テストツール 12 選

フロントエンド開発者にとって、さまざまな主要ブラウザのさまざまなバージョンでコードが適切に動作するこ...

Vueはキャンバスを使用して画像圧縮アップロードを実現します

この記事では、キャンバスを使用して画像圧縮アップロードを実現するVueの具体的なコードを参考までに共...

Windowsにmysql5.7をインストールする方法

まずmysqlの圧縮バージョンをダウンロードします。公式ダウンロードアドレスは123WORDPRES...

DockerがMySQL構成実装プロセスを開始

目次実際の戦闘プロセスまずは主なコマンドと詳細を一つずつ説明しましょう起動が成功したかどうかを確認す...

虫眼鏡効果を実現するJavaScript

この記事では、虫眼鏡効果を実現するためのJavaScriptの具体的なコードを参考までに紹介します。...

Oracle VM VirtualBox の CentOS7 オペレーティング システムのインストール チュートリアル図

目次インストール手順環境設定実行構成インストール手順ダウンロードアドレス: バージョン6.0 最初に...

HTML テーブルの行間および列間の操作 (rowspan、colspan)

一般的に、<td> 要素の colspan 属性はセルの列間操作を実装するために使用され...

Dockerはイメージ名とTAG操作の名前を変更します

docker イメージを使用する場合、以下に示すように、REPOSITORY と TAG の両方が ...

Linux で測位バックグラウンド サービスが時々クラッシュする問題の解決方法

問題の説明最近のバックグラウンドサービスでは、特定の命令の要求データをディスクに保存する新しい機能が...

Apache ab を使用して HTTP パフォーマンス テストを実行する

MacにはApache環境が付属していますターミナルを開き、sudo apachectl -v と入...