最新の JavaScript で非同期タスクを書く方法

最新の JavaScript で非同期タスクを書く方法

序文

この記事では、非同期 JavaScript の進化と、それがコードの記述方法にどのような変化をもたらすかについて説明します。 Web 開発の初期の段階から始めて、最新の非同期パターンまで学習します。

プログラミング言語としての JavaScript には、コードの動作を理解する上で非常に重要な 2 つの主な特性があります。 1 つ目は同期性であり、コードが行ごとに実行されることを意味します。2 つ目はシングル スレッドであり、一度に 1 つのコマンドのみが実行されることです。

言語が進化するにつれて、非同期実行を可能にする新しい成果物が登場します。開発者は、より複雑なアルゴリズムやデータフローを解決する際にさまざまなアプローチを試し、新しいインターフェースやパターンを生み出しました。

同期実行とオブザーバーパターン

はじめに述べたように、JavaScript は通常、記述したコードを 1 行ずつ実行します。最初の数年間でさえ、この言語にはこのルールの例外がいくつかありましたが、おそらくすでにご存知のとおり、HTTP リクエスト、DOM イベント、時間間隔などです。

イベント リスナーを追加してユーザーによる要素のクリックに応答すると、実行されていた言語インタープリターが停止し、リスナー コールバックに記述されたコードを実行してから通常のフローに戻ります。

間隔やネットワーク リクエストと同様に、addEventListener、setTimeout、および XMLHttpRequest は、Web 開発者が非同期実行にアクセスできる最初の成果物です。

これらは JavaScript における同期実行の例外ですが、言語が依然としてシングルスレッドであることを理解することが重要です。この同期を解除することはできますが、インタープリターはコードを 1 行ずつ実行し続けます。

たとえば、ネットワーク要求を検査します。

var リクエスト = 新しい XMLHttpRequest();
リクエストを開きます('GET'、'//some.api.at/server'、true);

// サーバーの応答を観察する
リクエスト.onreadystatechange = 関数() {
 (request.readyState === 4 && xhr.status === 200)の場合{
 console.log(リクエストの応答テキスト);
 }
}

11リクエストを送信します。

何が起こったとしても、サーバーが復旧すると、フェッチャーのコード シーケンスの前に onreadystatechange に割り当てられたメソッドが呼び出されます。

ユーザーの操作に反応する場合にも同様の状況が発生します。

ボタン = document.querySelector('ボタン');

// ユーザーの操作を観察する
button.addEventListener('click', 関数(e) {
 console.log('ユーザークリックが発生しました!');
})

外部イベントに接続し、イベントが発生したときに何を実行するかをコードに指示するコールバックを渡していることに気付くかもしれません。 10 年以上前、「コールバックとは何ですか?」は、多くのコード ベースのいたるところにこのパターンがあったため、面接で非常によく聞かれる質問でした。

いずれの場合も、私たちは外部の出来事に対応しています。特定の時間間隔、ユーザーアクション、またはサーバー応答に到達した場合などです。非同期タスクを独自に作成することはできませんが、スコープ外で発生するイベントを常に監視します。

そのため、このコーディング スタイルはオブザーバー パターンと呼ばれ、この場合は addEventListener インターフェイスによって最もよく表されます。すぐに、このパターンを公開するイベント エミッター ライブラリやフレームワークが普及し始めました。

NODE.JS とイベントエミッター

Node.js は良い例です。公式 Web サイトでは、Node.js を「非同期イベント駆動型 JavaScript ランタイム」と説明しているため、イベント エミッターとコールバックは第一級オブジェクトです。 EventEmitter コンストラクターも実装されています。

const EventEmitter = require('events');
const エミッター = 新しい EventEmitter();

// イベントに応答する
エミッター.on('greeting', (メッセージ) => console.log(メッセージ));

// イベントを送信する
エミッター.emit('greeting', 'こんにちは!');

これは非同期実行の一般的な方法であるだけでなく、そのエコシステムの中核となるパターンと規則でもあります。 Node.js は、Web 以外でもさまざまな環境で JavaScript を記述できる新しい時代を切り開きました。もちろん、新しいディレクトリの作成やファイルの書き込みなど、非同期の状況も可能です。

const { mkdir, writeFile } = require('fs');

const スタイル = 'body { 背景: #ffdead; }';

mkdir('./assets/', (エラー) => {
 もしエラーなら
 writeFile('assets/main.css', スタイル, 'utf-8', (エラー) => {
  if (!error) console.log('スタイルシートが作成されました');
 })
 }
})

コールバック関数は最初の引数としてエラーを受け取り、予期される応答データがあった場合はそれを 2 番目の引数として受け取ることに気付くかもしれません。これはエラーファースト コールバック パターンと呼ばれ、パッケージやライブラリの作成者や貢献者が作成した規則になりました。

Promiseと無限のコールバックチェーン

Web 開発がより複雑な問題に直面するにつれて、より優れた非同期成果物の必要性が生まれます。最後のコード スニペットを見ると、タスクの数が増えてもうまくスケーリングされない繰り返しコールバック チェーンがあることがわかります。

たとえば、ファイルの読み取りとスタイルの前処理という 2 つの手順のみを追加します。

const { mkdir、writeFile、readFile } = require('fs');
定数 less = require('less')

readFile('./main.less', 'utf-8', (エラー, データ) => {
 if (エラー) エラーをスローする
 less.render(データ、(lessError、出力) => {
 if (lessError) は lessError をスローします
 mkdir('./assets/', (dirError) => {
  (dirError) の場合、dirError をスローします。
  writeFile('assets/main.css', output.css, 'utf-8', (writeError) => {
  (writeError) の場合、writeError をスローします。
  console.log('スタイルシートが作成されました');
  })
 })
 })
16})

複数のコールバック チェーンとエラー処理の繰り返しにより、プログラムの記述がますます複雑になり、コードが理解しにくくなることがわかります。

プロミス、ラッパー、チェーンパターン

Promise が JavaScript 言語への新しい追加機能として初めて発表されたとき、あまり注目されませんでした。他の言語では何十年も前から同様の実装が行われてきたため、Promise は新しい概念ではありません。実際、このツールがリリースされて以来、私が取り組んできたほとんどのプロジェクトのセマンティクスと構造は変化しました。

Promises は、開発者が非同期コードを記述するための組み込みソリューションを導入しただけでなく、Web 開発の新しいフェーズを導き、フェッチなどの Web 仕様の後の新機能が構築される基盤となりました。

コールバック アプローチから Promise ベースのアプローチへの移行は、ライブラリやブラウザーなどのプロジェクトでますます一般的になりつつあり、Node.js も徐々に移行し始めています。

たとえば、Node の readFile メソッドをラップします。

const { readFile } = require('fs');

const asyncReadFile = (パス、オプション) => {
 新しい Promise を返します ((resolve, reject) => {
  readFile(パス、オプション、(エラー、データ) => {
   if (エラー) 拒否(エラー);
   そうでない場合は、(データ)を解決します。
  })
 });
}

ここでは、Promise コンストラクター内で実行してコールバックを非表示にし、成功した場合は解決を呼び出し、エラー オブジェクトを定義して拒否します。

メソッドが Promise オブジェクトを返す場合、解決される Promise の値 (この場合はデータ) を引数とする関数を then に渡すことで、その解決が成功したかどうかを追跡できます。

メソッドの実行中にエラーがスローされた場合、catch 関数 (存在する場合) が呼び出されます。

注: Promise の仕組みをより深く理解する必要がある場合は、Google の Web 開発ブログにある Jake Archibald の記事「JavaScript Promises: An Introduction」を読むことをお勧めします。

これで、これらの新しいメソッドを使用して、コールバック チェーンを回避できるようになりました。

非同期読み取り('./main.less', 'utf-8')
 .then(data => console.log('ファイルの内容', data))
 .catch(error => console.error('問題が発生しました', error))

非同期タスクを作成し、明確なインターフェースでその結果を追跡するためのネイティブ メソッドがあり、オブザーバー パターンを排除します。 Promise ベースのコードは、読みにくくエラーが発生しやすいコードに対する解決策であると思われます。

より優れた構文の強調表示とより明確なエラー メッセージによってコーディング プロセスがサポートされるため、開発者はより予測しやすく、理解しやすくパフォーマンスに優れたコードを記述できるようになり、潜在的な落とし穴を見つけやすくなります。

Promise の採用はコミュニティ内で非常に広まったため、Node.js は fs.promises からのファイル操作のインポートなど、Promise オブジェクトを返す I/O メソッドの組み込みバージョンをすぐにリリースしました。

エラーファースト コールバック パターンに従う関数をラップし、Promise ベースの関数に変換する promisify ツールも提供されます。

しかし、Promise はすべての場合に役立つのでしょうか?

Promise を使用して記述されたスタイル前処理タスクを再評価してみましょう。

const { mkdir, writeFile, readFile } = require('fs').promises;
定数 less = require('less')

ファイルを読み込む('./main.less', 'utf-8')
 .then(less.render)
 .then(結果 =>
  mkdir('./assets')
   .then(writeFile('assets/main.css', result.css, 'utf-8'))
 )
 .catch(エラー => console.error(エラー))

catch を使用するようになったため、特にエラー処理に関しては、コードの冗長性は明らかに低下していますが、Promise では、アクションの連鎖に直接関連するコードの明確なインデントを提供できていません。

実際、これは readFile の呼び出し後の最初の then ステートメントで実装されています。これらのコード行の後に何が起こるかというと、最初にディレクトリを作成し、次に結果をファイルに書き込むことができる新しいスコープが作成されます。その結果、インデントのリズムが崩れ、一目見ただけでは命令の順序を判断することが難しくなります。

注意: これはサンプル プログラムであり、特定のメソッドは当社が管理しており、すべて業界の慣行に従っていますが、常にそうであるとは限りません。私たちのコーディング スタイルは、より複雑な連結や異なるライブラリの導入によって簡単に破られる可能性があります。

幸いなことに、JavaScript コミュニティは再び他の言語の構文から学び、同期コードほど読みやすくなくても、ほとんどの場合に非同期タスクを連鎖させるのに役立つ表記法を追加しました。

非同期と待機

Promise は実行時に未解決の値として定義され、Promise インスタンスの作成はこの成果物への「明示的な」呼び出しになります。

const { mkdir、writeFile、readFile } = require('fs').promises;
定数 less = require('less')

ファイルを読み込む('./main.less', 'utf-8')
 .then(less.render)
 .then(結果 =>
  mkdir('./assets')
   .then(writeFile('assets/main.css', result.css, 'utf-8'))
 )
 .catch(エラー => console.error(エラー))

非同期メソッド内では、実行を続行する前に Promise の解決を決定するために予約語 await を使用できます。

この構文を使用してコード スニペットを書き直してみましょう。

const { mkdir、writeFile、readFile } = require('fs').promises;
定数 less = require('less')

非同期関数processLess() {
 const コンテンツ = readFile('./main.less', 'utf-8') を待機します
 const 結果 = less.render(content) を待機します
 mkdir('./assets') を待機します。
 writeFile('assets/main.css', result.css, 'utf-8') を待機します。
}

11プロセスレス()

注: async 関数のスコープ外では await を使用できないため、すべてのコードをメソッドに移動する必要があることに注意してください。

非同期メソッドは、await ステートメントを見つけるたびに、promise が解決されるまで実行を停止します。

非同期で実行されるにもかかわらず、async/await で表現すると、コードは同期しているように見え、開発者にとって読みやすく理解しやすいものになります。

エラー処理はどうですか?言語に長い間存在してきた try と catch を使用できます。

const { mkdir、writeFile、readFile } = require('fs').promises;
定数 less = require('less')

非同期関数processLess() {
 const コンテンツ = readFile('./main.less', 'utf-8') を待機します
 const 結果 = less.render(content) を待機します
 mkdir('./assets') を待機します。
 writeFile('assets/main.css', result.css, 'utf-8') を待機します。
}

試す {
 プロセスレス()
} キャッチ (e) {
 コンソールエラー(e)
}

手順中にスローされたエラーは catch ステートメント内のコードによって処理されるので安心です。これで、読みやすく明確なコードができました。

戻り値に対する後続の操作は、コードのリズムを乱さない mkdir などの変数に格納する必要はありません。また、後のステップで result の値にアクセスするために新しいスコープを作成する必要もありません。

Promise は言語に導入された基本的なアーティファクトであり、JavaScript で async/await 表記を有効にするために必要であり、最新のブラウザーと最新バージョンの Node.js で使用できると言っても過言ではありません。

注: 最近 JSConf で、Node の作成者であり最初の貢献者である Ryan Dahl 氏は、初期の開発で Promise に準拠しなかったことを後悔していると述べました。主な理由は、Node の目標がイベント駆動型のサーバーとファイル マネージャーを作成することであり、これには Observer パターンの方が適していたためです。

結論は

Web 開発への Promise の導入は、コード内での操作の順序付け方法を変更することを目的としており、コードの理解方法やライブラリやパッケージの作成方法も変更されました。

しかし、コールバック チェーンを取り除くのは解決が難しく、Node.js などの採用されたオブザーバー パターンやアプローチに何年も慣れてきた後では、メソッドを then に渡す必要があることは、コールバック チェーンを取り除くのに役立たないと思います。

Nolan Lawson 氏が素晴らしい記事「Promise のカスケーディングに問題がある」で説明しているように、古いコールバックの習慣は固く、固定されています。その中で彼は、これらの落とし穴を避ける方法を説明しています。

Promise は、非同期タスクを自然に生成できるようにする中間ステップですが、より優れたコード パターンに向けての進歩には役立たず、改善された言語構文に慣れる必要がある場合もあります。

JavaScript でより複雑な問題を解決しようとすると、より成熟した言語の必要性がわかり、これまでオンラインでは見たことのないアーキテクチャやパターンを試しました。

私たちは JavaScript ガバナンスを Web を超えて拡大し続け、より複雑なパズルを解こうとしていますが、数年後に ECMAScript 仕様がどのようになっているかはまだわかりません。

現時点では、これらの難しい問題を本当に単純なプログラムに変換するために言語に何が必要なのかを言うのは難しいですが、Web と JavaScript 自体が技術を前進させ、課題や新しい環境に適応しようとしていることには満足しています。ブラウザで初めてコードを書き始めた 10 年前と比べて、JavaScript ははるかに「非同期対応」になっているように感じます。

元記事: https://www.smashingmagazine.com/2019/10/asynchronous-tasks-modern-javascript/

これで、最新の JavaScript で非同期タスクを記述する方法に関するこの記事は終了です。JavaScript で非同期タスクを記述する方法の詳細については、123WORDPRESS.COM の以前の記事を検索するか、次の関連記事を引き続き参照してください。今後とも 123WORDPRESS.COM をよろしくお願いいたします。

以下もご興味があるかもしれません:
  • シングルスレッドJavaScriptにおける非同期処理実装の詳細な説明
  • JSシングルスレッド非同期IOコールバックの特性を分析する
  • Javascript 非同期プログラミング: Promise を本当に理解していますか?
  • JavaScript 非同期プログラミングにおける Promise の初期の使用法の詳細な説明
  • JS 非同期実行の原則とコールバックの詳細
  • Node.js の非同期ジェネレータと非同期反復の詳細な説明
  • 1 つの記事で Node.js の非同期プログラミングを学ぶ
  • Node.js における非同期プログラミングの知識ポイントの詳細な説明
  • JS の 3 つの主要な問題、非同期性とシングルスレッドについて簡単に説明します。

<<:  Linux でシステム ディスクを初期化した後にデータ ディスクを再マウントする方法

>>:  Centos7 インストール mysql5.6.29 シェル スクリプト

推薦する

MySQL スロークエリ関連パラメータの原理の分析

MySQL スロー クエリ (正式名称はスロー クエリ ログ) は、MySQL によって提供されるロ...

HTML ファイルにフラッシュ ビデオ形式 (flv、swf) ファイルを埋め込む方法

Flash ファイル形式: .FLV および .SWFフラッシュ ビデオ形式には、.flv と .s...

docker compose を使用して FastDfs ファイル サーバーをインストールする詳細な例

ドッカーの作成 バージョン: '2' サービス: fastdfsトラッカー: ホスト...

Html+CSS 描画三角形アイコン

まずはレンダリングを見てみましょう: XML/HTML コードコンテンツをクリップボードにコピー&l...

Windows での自動展開に Jenkins を使用するチュートリアル図

今日は、Jenkins + powershell スクリプトを使用して、.NET CORE スクリプ...

MySQL 8の新機能ウィンドウ関数の役割

MySQL 8.0 の新機能は次のとおりです。 Unicode 9.0 をすぐに完全にサポートウィン...

Vite2.0の落とし穴

目次Viteプロジェクトビルドの最適化他のやっとこれは前回の記事の補足です。設定プロジェクトで遭遇し...

Dockerの一般的なコマンドとヒントのまとめ

インストールスクリプトUbuntu / CentOS Debian のインストールに問題があるようで...

MySQL 5.7.16 無料インストール版のインストールと設定方法のグラフィックチュートリアル

この記事ではMySQL 5.7.16のインストールと設定方法を記録します。具体的な内容は以下のとおり...

Angularプロジェクトにおける共有モジュールの実装の詳細な説明

目次1. 共有共通モジュール2. 共有マテリアルモジュール3. 共有確認ダイアログ1. 共有共通モジ...

MySQL パラメータ関連の概念とクエリ変更方法

序文:以前の記事では、特定のパラメータの機能についてよく紹介してきました。しかし、MySQL パラメ...

JS の原価と基準価額の問題に関する簡単な分析

プリミティブ値 -> プリミティブ型Number String Boolean undefin...

CSS マスクを使用して PNG 画像のサイズを大幅に最適化します (推奨)

この記事は共有および集約することを歓迎します。全文を転載する必要はありません。著作権を尊重してくださ...

今日は、珍しいけれど役に立つJSテクニックをいくつか紹介します

1. 戻るボタンhistory.back() を使用してブラウザの「戻る」ボタンを作成します。 &l...

JavaScriptの基本構文とデータ型の詳細な説明

目次JavaScript のインポート1. 内部ラベル2. 外部紹介基本的な構文データ型番号弦ブール...