ホームページ ウェブフロントエンド jsチュートリアル Node.js のコールバックとコード設計パターンの分析非同期プログラミング_node.js

Node.js のコールバックとコード設計パターンの分析非同期プログラミング_node.js

May 16, 2016 pm 03:15 PM
node 折り返し電話 非同期

NodeJS の最大のセールスポイントであるイベント メカニズムと非同期 IO は、開発者にとっては透過的ではありません。開発者はこのセールスポイントを利用するにはコードを非同期で記述する必要がありますが、これは一部の NodeJS 反対派から批判されています。しかし、何はともあれ、非同期プログラミングは確かに NodeJS の最大の特徴です。非同期プログラミングをマスターしなければ、本当に NodeJS を学んだとは言えません。この章では、非同期プログラミングに関するさまざまな知識を紹介します。

コードでは、非同期プログラミングを直接表現するのはコールバックです。非同期プログラミングはコールバックに依存しますが、コールバックを使用したからといってプログラムが非同期になるとは言えません。まず次のコードを見てみましょう。

function heavyCompute(n, callback) {
 var count = 0,
  i, j;

 for (i = n; i > 0; --i) {
  for (j = n; j > 0; --j) {
   count += 1;
  }
 }

 callback(count);
}

heavyCompute(10000, function (count) {
 console.log(count);
});

console.log('hello');

ログイン後にコピー
100000000
hello
ログイン後にコピー

ご覧のとおり、上記のコードのコールバック関数は後続のコードの前に実行されます。 JS 自体は単一のスレッドで実行され、コードの一部の実行が終了する前に他のコードを実行することは不可能であるため、非同期実行の概念はありません。

ただし、関数の実行内容が、別のスレッドまたはプロセスを作成し、JS メイン スレッドと並行して何かを実行し、それが完了したときに JS メイン スレッドに通知することである場合、状況は異なります。次のコードを見てみましょう。

setTimeout(function () {
 console.log('world');
}, 1000);

console.log('hello');

ログイン後にコピー
hello
world
ログイン後にコピー


今回は、後続のコードの後に​​コールバック関数が実行されていることがわかります。前述したように、JS 自体はシングルスレッドであり、非同期実行はできません。そのため、setTimeout などの JS 仕様外の実行環境が提供する特別な関数は、並列スレッドを作成してすぐに返すことであり、 JS マスターからプロセスは、並列プロセスから通知を受信した後、後続のコードを実行し、コールバック関数を実行できます。このような関数には、setTimeout や setInterval などの一般的な関数に加えて、fs.readFile などの NodeJS によって提供される非同期 API も含まれます。

さらに、JS は単一のスレッドで実行されるという事実に戻ります。これにより、JS はコードの一部を実行する前に、コールバック関数を含む他のコードを実行できないことが決まります。つまり、並列スレッドが作業を完了し、JS メイン スレッドにコールバック関数を実行するように通知した場合でも、JS メイン スレッドがアイドル状態になるまでコールバック関数は実行を開始しません。以下はその一例です。

function heavyCompute(n) {
 var count = 0,
  i, j;

 for (i = n; i > 0; --i) {
  for (j = n; j > 0; --j) {
   count += 1;
  }
 }
}

var t = new Date();

setTimeout(function () {
 console.log(new Date() - t);
}, 1000);

heavyCompute(50000);

ログイン後にコピー
8520
ログイン後にコピー


ご覧のとおり、JS メイン スレッドが他のコードの実行でビジー状態だったために、1 秒後に呼び出されるはずだったコールバック関数の実際の実行時間は大幅に遅れました。

コード設計パターン
非同期プログラミングには、同じ機能を実現するために、同期モードと非同期モードで記述されるコードは大きく異なります。よくあるパターンをいくつか紹介します。

関数の戻り値
同期モードでは、ある関数の出力を別の関数の入力として使用することが非常に一般的な要件であり、コードは通常次のように記述されます。

var output = fn1(fn2('input'));
// Do something.
ログイン後にコピー
非同期モードでは、関数の実行結果が戻り値ではなくコールバック関数を介して渡されるため、通常は次のようにコードを記述します。

fn2('input', function (output2) {
 fn1(output2, function (output1) {
  // Do something.
 });
});
ログイン後にコピー
ご覧のとおり、このメソッドは 1 つのコールバック関数内にネストされた 1 つのコールバック関数です。数が多すぎると、> 型のコードを作成するのが簡単になります。

配列を走査する

配列を走査するとき、関数を使用してデータ メンバーに対して何らかの処理を順番に実行することも一般的な要件です。関数が同期的に実行される場合、通常は次のコードが記述されます:

var len = arr.length,
 i = 0;

for (; i < len; ++i) {
 arr[i] = sync(arr[i]);
}

// All array items have processed.

ログイン後にコピー
関数が非同期で実行される場合、上記のコードはループ終了後にすべての配列メンバーが処理されたことを保証できません。配列メンバーを次々にシリアルに処理する必要がある場合、非同期コードは通常次のように記述されます:

(function next(i, len, callback) {
 if (i < len) {
  async(arr[i], function (value) {
   arr[i] = value;
   next(i + 1, len, callback);
  });
 } else {
  callback();
 }
}(0, arr.length, function () {
 // All array items have processed.
}));
ログイン後にコピー
ご覧のとおり、上記のコードは次の配列メンバーを渡すだけであり、非同期関数が 1 回実行された後に次の実行ラウンドを開始し、すべての配列メンバーが処理されるまで後続のコードが実行されます。コールバックを通じてトリガーされます。

配列メンバーを並列処理できるが、後続のコードでは実行前にすべての配列メンバーを処理する必要がある場合、非同期コードは次の形式に調整されます:

(function (i, len, count, callback) {
 for (; i < len; ++i) {
  (function (i) {
   async(arr[i], function (value) {
    arr[i] = value;
    if (++count === len) {
     callback();
    }
   });
  }(i));
 }
}(0, arr.length, 0, function () {
 // All array items have processed.
}));
ログイン後にコピー
ご覧のとおり、非同期シリアル トラバーサル バージョンと比較すると、上記のコードはすべての配列メンバーを並列処理し、カウンタ変数を使用してすべての配列メンバーがいつ処理されたかを判断します。

例外処理

JS 自体によって提供される例外のキャッチおよび処理メカニズム - try..catch.. は、同期的に実行されるコードにのみ使用できます。以下に例を示します。

function sync(fn) {
 return fn();
}

try {
 sync(null);
 // Do something.
} catch (err) {
 console.log('Error: %s', err.message);
}

ログイン後にコピー
Error: object is not a function
ログイン後にコピー
ログイン後にコピー
ご覧のとおり、例外は最初の try ステートメントに遭遇したときにキャッチされるまで、コード実行パスに沿ってバブルします。ただし、非同期関数はコードの実行パスを中断するため、非同期関数の実行中および実行後に生成された例外が実行パスが中断された場所までバブルアップすると、try ステートメントが見つからなかった場合、グローバル例外としてスローされます。 。以下に例を示します。

function async(fn, callback) {
 // Code execution path breaks here.
 setTimeout(function () {
  callback(fn());
 }, 0);
}

try {
 async(null, function (data) {
  // Do something.
 });
} catch (err) {
 console.log('Error: %s', err.message);
}

ログイン後にコピー

因为代码执行路径被打断了,我们就需要在异常冒泡到断点之前用 try 语句把异常捕获住,并通过回调函数传递被捕获的异常。于是我们可以像下边这样改造上边的例子。

function async(fn, callback) {
 // Code execution path breaks here.
 setTimeout(function () {
  try {
   callback(null, fn());
  } catch (err) {
   callback(err);
  }
 }, 0);
}

async(null, function (err, data) {
 if (err) {
  console.log('Error: %s', err.message);
 } else {
  // Do something.
 }
});

ログイン後にコピー
Error: object is not a function
ログイン後にコピー
ログイン後にコピー

可以看到,异常再次被捕获住了。在 NodeJS 中,几乎所有异步 API 都按照以上方式设计,回调函数中第一个参数都是 err。因此我们在编写自己的异步函数时,也可以按照这种方式来处理异常,与 NodeJS 的设计风格保持一致。

有了异常处理方式后,我们接着可以想一想一般我们是怎么写代码的。基本上,我们的代码都是做一些事情,然后调用一个函数,然后再做一些事情,然后再调用一个函数,如此循环。如果我们写的是同步代码,只需要在代码入口点写一个 try 语句就能捕获所有冒泡上来的异常,示例如下。

function main() {
 // Do something.
 syncA();
 // Do something.
 syncB();
 // Do something.
 syncC();
}

try {
 main();
} catch (err) {
 // Deal with exception.
}

ログイン後にコピー

但是,如果我们写的是异步代码,就只有呵呵了。由于每次异步函数调用都会打断代码执行路径,只能通过回调函数来传递异常,于是我们就需要在每个回调函数里判断是否有异常发生,于是只用三次异步函数调用,就会产生下边这种代码。

function main(callback) {
 // Do something.
 asyncA(function (err, data) {
  if (err) {
   callback(err);
  } else {
   // Do something
   asyncB(function (err, data) {
    if (err) {
     callback(err);
    } else {
     // Do something
     asyncC(function (err, data) {
      if (err) {
       callback(err);
      } else {
       // Do something
       callback(null);
      }
     });
    }
   });
  }
 });
}

main(function (err) {
 if (err) {
  // Deal with exception.
 }
});

ログイン後にコピー

可以看到,回调函数已经让代码变得复杂了,而异步方式下对异常的处理更加剧了代码的复杂度。

このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。

ホットAIツール

Undresser.AI Undress

Undresser.AI Undress

リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover

AI Clothes Remover

写真から衣服を削除するオンライン AI ツール。

Undress AI Tool

Undress AI Tool

脱衣画像を無料で

Clothoff.io

Clothoff.io

AI衣類リムーバー

Video Face Swap

Video Face Swap

完全無料の AI 顔交換ツールを使用して、あらゆるビデオの顔を簡単に交換できます。

ホットツール

メモ帳++7.3.1

メモ帳++7.3.1

使いやすく無料のコードエディター

SublimeText3 中国語版

SublimeText3 中国語版

中国語版、とても使いやすい

ゼンドスタジオ 13.0.1

ゼンドスタジオ 13.0.1

強力な PHP 統合開発環境

ドリームウィーバー CS6

ドリームウィーバー CS6

ビジュアル Web 開発ツール

SublimeText3 Mac版

SublimeText3 Mac版

神レベルのコード編集ソフト(SublimeText3)

Express を使用してノード プロジェクトでファイルのアップロードを処理する方法 Express を使用してノード プロジェクトでファイルのアップロードを処理する方法 Mar 28, 2023 pm 07:28 PM

ファイルのアップロードをどのように処理するか?次の記事では、Express を使用してノード プロジェクトでファイルのアップロードを処理する方法を紹介します。

Nodeのプロセス管理ツール「pm2」を徹底分析 Nodeのプロセス管理ツール「pm2」を徹底分析 Apr 03, 2023 pm 06:02 PM

この記事では、Node のプロセス管理ツール「pm2」について説明し、pm2 が必要な理由、pm2 のインストール方法と使用方法について説明します。皆様のお役に立てれば幸いです。

PIノードティーチング:PIノードとは何ですか? PIノードをインストールしてセットアップする方法は? PIノードティーチング:PIノードとは何ですか? PIノードをインストールしてセットアップする方法は? Mar 05, 2025 pm 05:57 PM

ピン張りのノードの詳細な説明とインストールガイドこの記事では、ピネットワークのエコシステムを詳細に紹介します - PIノードは、ピン系生態系における重要な役割であり、設置と構成の完全な手順を提供します。 Pinetworkブロックチェーンテストネットワークの発売後、PIノードは多くの先駆者の重要な部分になり、テストに積極的に参加し、今後のメインネットワークリリースの準備をしています。まだピン張りのものがわからない場合は、ピコインとは何かを参照してください。リストの価格はいくらですか? PIの使用、マイニング、セキュリティ分析。パインワークとは何ですか?ピン競技プロジェクトは2019年に開始され、独占的な暗号通貨PIコインを所有しています。このプロジェクトは、誰もが参加できるものを作成することを目指しています

クイックアプリケーション: PHP 複数ファイルの非同期 HTTP ダウンロードの実践的な開発事例分析 クイックアプリケーション: PHP 複数ファイルの非同期 HTTP ダウンロードの実践的な開発事例分析 Sep 12, 2023 pm 01:15 PM

クイック アプリケーション: PHP の実践的な開発ケース分析 複数ファイルの非同期 HTTP ダウンロード インターネットの発展に伴い、ファイル ダウンロード機能は多くの Web サイトやアプリケーションの基本的なニーズの 1 つになりました。複数のファイルを同時にダウンロードする必要があるシナリオでは、従来の同期ダウンロード方法は非効率的で時間がかかることがよくあります。このため、PHP を使用して HTTP 経由で複数のファイルを非同期にダウンロードするソリューションがますます一般的になってきています。この記事では、実際の開発事例を通して、PHP 非同期 HTTP の使用方法を詳しく分析します。

Angular と Node を使用したトークンベースの認証 Angular と Node を使用したトークンベースの認証 Sep 01, 2023 pm 02:01 PM

認証は、Web アプリケーションの最も重要な部分の 1 つです。このチュートリアルでは、トークンベースの認証システムと、それが従来のログイン システムとどのように異なるかについて説明します。このチュートリアルを終えると、Angular と Node.js で書かれた完全に動作するデモが表示されます。従来の認証システム トークンベースの認証システムに進む前に、従来の認証システムを見てみましょう。ユーザーはログイン フォームにユーザー名とパスワードを入力し、[ログイン] をクリックします。リクエストを行った後、データベースにクエリを実行してバックエンドでユーザーを認証します。リクエストが有効な場合、データベースから取得したユーザー情報を使用してセッションが作成され、セッション情報が応答ヘッダーで返され、セッション ID がブラウザに保存されます。対象となるアプリケーションへのアクセスを提供します。

Swoole が非同期 SMTP 操作をサポートする方法 Swoole が非同期 SMTP 操作をサポートする方法 Jun 25, 2023 pm 12:24 PM

インターネットの継続的な発展と普及に伴い、電子メールは人々の生活や仕事に欠かせないものとなり、SMTP (Simple Mail Transfer Protocol) は電子メール送信のための重要なプロトコルの 1 つです。 PHP の非同期ネットワーク通信フレームワークとして、Swoole は非同期 SMTP 操作を十分にサポートし、電子メール送信をより効率的かつ安定させます。この記事では、Swoole が同期の使用を含む非同期 SMTP 操作をどのようにサポートするかを紹介します。

Swoole が非同期 AMQP 操作をサポートする方法 Swoole が非同期 AMQP 操作をサポートする方法 Jun 25, 2023 am 08:22 AM

インターネット ビジネスの量が増え続けるにつれて、高い同時実行性と高性能に対する要求がますます高まっており、PHP のネットワーク通信フレームワークとしての Swoole が開発者の間でますます支持されています。その中でも、Swoole は、より一般的なアプリケーション シナリオの 1 つである非同期 AMQP をサポートしています。それでは、Swoole が非同期 AMQP 操作をどのようにサポートするかを見てみましょう。まず、AMQP とは何かを明確にする必要があります。 AMQP (AdvancedMessageQueuingProtocol) アドバンスト

Python asyncio の上級ガイド: 初心者から専門家まで Python asyncio の上級ガイド: 初心者から専門家まで Mar 04, 2024 am 09:43 AM

同時プログラミングと非同期プログラミング 同時プログラミングは、同時に実行される複数のタスクを扱います。非同期プログラミングは、タスクがスレッドをブロックしない同時プログラミングの一種です。 asyncio は Python の非同期プログラミング用のライブラリで、プログラムがメイン スレッドをブロックせずに I/O 操作を実行できるようにします。イベント ループ asyncio の中核は、I/O イベントを監視し、対応するタスクをスケジュールするイベント ループです。コルーチンの準備が完了すると、イベント ループは I/O 操作を待つまでそのコルーチンを実行します。その後、コルーチンを一時停止し、他のコルーチンの実行を継続します。コルーチン コルーチンは、実行を一時停止および再開できる関数です。 asyncdef キーワードは、コルーチンの作成に使用されます。コルーチンは await キーワードを使用して、I/O 操作が完了するのを待ちます。 asyncio の次の基本

See all articles