Node.jsで非同期コードを書く方法

提供:Dev Guides
移動先:案内検索

著者は、 Open Internet / Free Speech Fund を選択して、 Write forDOnationsプログラムの一環として寄付を受け取りました。

序章

JavaScriptの多くのプログラムでは、コードは開発者が1行ずつ書き込むときに実行されます。 これは、同期実行と呼ばれます。これは、行が書き込まれた順序で次々に実行されるためです。 ただし、コンピュータに与えるすべての指示にすぐに対応する必要はありません。 たとえば、ネットワークリクエストを送信する場合、コードを実行するプロセスは、データが返されるのを待ってから処理する必要があります。 この場合、ネットワーク要求が完了するのを待っている間に他のコードを実行しなかった場合、時間が無駄になります。 この問題を解決するために、開発者は非同期プログラミングを使用します。このプログラミングでは、コードの行が記述された順序とは異なる順序で実行されます。 非同期プログラミングを使用すると、ネットワーク要求などの長いアクティビティが終了するのを待つ間に、他のコードを実行できます。

JavaScriptコードは、コンピュータープロセス内の単一のスレッドで実行されます。 そのコードはこのスレッドで同期的に処理され、一度に1つの命令のみが実行されます。 したがって、このスレッドで長時間実行されるタスクを実行する場合、タスクが完了するまで残りのコードはすべてブロックされます。 JavaScriptの非同期プログラミング機能を活用することで、この問題を回避するために、実行時間の長いタスクをバックグラウンドスレッドにオフロードできます。 タスクが完了すると、タスクのデータを処理するために必要なコードがメインのシングルスレッドに戻されます。

このチュートリアルでは、JavaScriptが Event Loop の助けを借りて非同期タスクを管理する方法を学習します。これは、別のタスクを待機しながら新しいタスクを完了するJavaScript構造です。 次に、非同期プログラミングを使用して Studio Ghibli API から映画のリストを要求し、データをCSVファイルに保存するプログラムを作成します。 非同期コードは、コールバック、promise、およびasync /awaitキーワードの3つの方法で記述されます。

注:この記事の執筆時点では、非同期プログラミングはコールバックのみを使用して実行されなくなりましたが、この廃止されたメソッドを学習することで、JavaScriptコミュニティがpromiseを使用する理由について優れたコンテキストを提供できます。 async / awaitキーワードを使用すると、promiseをより簡潔な方法で使用できるため、この記事の執筆時点では、JavaScriptで非同期プログラミングを行うための標準的な方法です。


前提条件

  • 開発マシンにインストールされているNode.js。 このチュートリアルでは、バージョン10.17.0を使用します。 これをmacOSまたはUbuntu18.04にインストールするには、Node.jsをインストールしてmacOSにローカル開発環境を作成する方法またはのPPAを使用したインストール]セクションの手順に従います。 Ubuntu18.04にNode.jsをインストールする方法。
  • また、プロジェクトへのパッケージのインストールにも精通している必要があります。 npmおよびpackage.jsonでNode.jsモジュールを使用する方法に関するガイドを読んで最新情報を入手してください。
  • 非同期で関数を使用する方法を学ぶ前に、JavaScriptで関数を作成して実行することに慣れていることが重要です。 紹介や復習が必要な場合は、JavaScriptで関数を定義する方法に関するガイドを読むことができます。

イベントループ

JavaScript関数実行の内部動作を研究することから始めましょう。 これがどのように動作するかを理解することで、非同期コードをより慎重に記述できるようになり、将来のコードのトラブルシューティングに役立ちます。

JavaScriptインタープリターがコードを実行すると、呼び出されるすべての関数がJavaScriptの呼び出しスタックに追加されます。 呼び出しスタックはスタックです。これはリストのようなデータ構造であり、アイテムは上部にのみ追加したり、上部から削除したりできます。 スタックは、「後入れ先出し」またはLIFOの原則に従います。 スタックに2つのアイテムを追加すると、最後に追加されたアイテムが最初に削除されます。

コールスタックを使用した例で説明しましょう。 JavaScriptが呼び出されている関数functionA()を検出すると、その関数は呼び出しスタックに追加されます。 その関数functionA()が別の関数functionB()を呼び出すと、functionB()が呼び出しスタックの一番上に追加されます。 JavaScriptが関数の実行を完了すると、その関数は呼び出しスタックから削除されます。 したがって、JavaScriptは最初にfunctionB()を実行し、完了したらスタックから削除してから、functionA()の実行を終了し、呼び出しスタックから削除します。 これが、内部関数が常に外部関数の前に実行される理由です。

JavaScriptは、ファイルへの書き込みなどの非同期操作に遭遇すると、そのファイルをメモリ内のテーブルに追加します。 このテーブルには、操作、実行が完了するための条件、および操作が完了したときに呼び出される関数が格納されます。 操作が完了すると、JavaScriptは関連する関数をメッセージキューに追加します。 キューは別のリストのようなデータ構造であり、アイテムは下部にのみ追加でき、上部から削除できます。 メッセージキューでは、2つ以上の非同期操作で機能を実行する準備ができている場合、最初に完了した非同期操作の機能が最初に実行対象としてマークされます。

メッセージキュー内の関数は、呼び出しスタックに追加されるのを待っています。 イベントループは、呼び出しスタックが空かどうかをチェックする永続的なプロセスです。 そうである場合、メッセージキューの最初のアイテムがコールスタックに移動されます。 JavaScriptは、コードで解釈する関数呼び出しよりもメッセージキュー内の関数を優先します。 コールスタック、メッセージキュー、およびイベントループの複合効果により、非同期アクティビティを管理しながらJavaScriptコードを処理できます。

イベントループの概要を理解したので、作成した非同期コードがどのように実行されるかがわかりました。 この知識があれば、コールバック、プロミス、async /awaitの3つの異なるアプローチで非同期コードを作成できるようになります。

コールバックを使用した非同期プログラミング

コールバック関数は、引数として別の関数に渡され、他の関数が終了したときに実行される関数です。 コールバックを使用して、非同期操作が完了した後にのみコードが実行されるようにします。

長い間、コールバックは非同期コードを作成するための最も一般的なメカニズムでしたが、コードの読み取りを混乱させる可能性があるため、現在ではほとんど廃止されています。 このステップでは、コールバックを使用した非同期コードの例を記述して、他の戦略の効率の向上を確認するためのベースラインとして使用できるようにします。

別の関数でコールバック関数を使用する方法はたくさんあります。 一般的に、彼らはこの構造を取ります:

function asynchronousFunction([ Function Arguments ], [ Callback Function ]) {
    [ Action ]
}

JavaScriptまたはNode.jsでは、外部関数の最後の引数としてコールバック関数を使用する必要はありませんが、コールバックを識別しやすくするのが一般的な方法です。 JavaScript開発者が無名関数をコールバックとして使用することも一般的です。 匿名関数は、名前なしで作成された関数です。 引数リストの最後に関数が定義されていると、通常ははるかに読みやすくなります。

コールバックを示すために、 StudioGhibliムービーのリストをファイルに書き込むNode.jsモジュールを作成しましょう。 まず、JavaScriptファイルとその出力を保存するフォルダーを作成します。

mkdir ghibliMovies

次に、そのフォルダに入ります。

cd ghibliMovies

まず、 Studio Ghibli API にHTTPリクエストを送信します。これにより、コールバック関数が結果をログに記録します。 これを行うには、コールバックでHTTP応答のデータにアクセスできるようにするライブラリをインストールします。

ターミナルでnpmを初期化して、後でパッケージの参照を取得できるようにします。

npm init -y

次に、requestライブラリをインストールします。

npm i request --save

次に、nanoなどのテキストエディタでcallbackMovies.jsという新しいファイルを開きます。

nano callbackMovies.js

テキストエディタで、次のコードを入力します。 requestモジュールを使用してHTTPリクエストを送信することから始めましょう。

callbackMovies.js

const request = require('request');

request('https://ghibliapi.herokuapp.com/films');

最初の行では、npmを介してインストールされたrequestモジュールをロードします。 モジュールは、HTTP要求を行うことができる関数を返します。 次に、その関数をrequest定数に保存します。

次に、request()関数を使用してHTTPリクエストを作成します。 強調表示された変更を追加して、HTTPリクエストからコンソールにデータを出力してみましょう。

callbackMovies.js

const request = require('request');

request('https://ghibliapi.herokuapp.com/films', (error, response, body) => {
    if (error) {
        console.error(`Could not send request to API: ${error.message}`);
        return;
    }

    if (response.statusCode != 200) {
        console.error(`Expected status code 200 but received ${response.statusCode}.`);
        return;
    }

    console.log('Processing our list of movies');
    movies = JSON.parse(body);
    movies.forEach(movie => {
        console.log(`${movie['title']}, ${movie['release_date']}`);
    });
});

request()関数を使用する場合、2つのパラメーターを指定します。

  • リクエストしようとしているウェブサイトのURL
  • リクエストの完了後にエラーまたは成功した応答を処理するコールバック関数

コールバック関数には、errorresponse、およびbodyの3つの引数があります。 HTTPリクエストが完了すると、結果に応じて引数に値が自動的に与えられます。 リクエストの送信に失敗した場合、errorにはオブジェクトが含まれますが、responsebodynullになります。 リクエストが正常に行われた場合、HTTP応答はresponseに保存されます。 HTTP応答がデータを返す場合(この例ではJSONを取得します)、データはbodyに設定されます。

コールバック関数は、最初にエラーを受け取ったかどうかを確認します。 最初にコールバックのエラーをチェックして、データが欠落した状態でコールバックの実行が続行されないようにすることをお勧めします。 この場合、エラーと関数の実行をログに記録します。 次に、応答のステータスコードを確認します。 私たちのサーバーは常に利用可能であるとは限らず、APIが変更されて、かつては賢明なリクエストが正しくなくなる可能性があります。 ステータスコードが200であること、つまりリクエストが「OK」であることを確認することで、私たちの応答が期待どおりであると確信できます。

最後に、Arrayに対する応答本文を解析し、各映画をループして、その名前とリリース年をログに記録します。

ファイルを保存して終了した後、次のコマンドを使用してこのスクリプトを実行します。

node callbackMovies.js

次の出力が得られます。

OutputCastle in the Sky, 1986
Grave of the Fireflies, 1988
My Neighbor Totoro, 1988
Kiki's Delivery Service, 1989
Only Yesterday, 1991
Porco Rosso, 1992
Pom Poko, 1994
Whisper of the Heart, 1995
Princess Mononoke, 1997
My Neighbors the Yamadas, 1999
Spirited Away, 2001
The Cat Returns, 2002
Howl's Moving Castle, 2004
Tales from Earthsea, 2006
Ponyo, 2008
Arrietty, 2010
From Up on Poppy Hill, 2011
The Wind Rises, 2013
The Tale of the Princess Kaguya, 2013
When Marnie Was There, 2014

スタジオジブリの映画が公開された年のリストを無事に受け取りました。 次に、現在ファイルにログインしているムービーリストを書き込んで、このプログラムを完成させましょう。

テキストエディタでcallbackMovies.jsファイルを更新して、次の強調表示されたコードを含めます。これにより、ムービーデータを含むCSVファイルが作成されます。

callbackMovies.js

const request = require('request');
const fs = require('fs');

request('https://ghibliapi.herokuapp.com/films', (error, response, body) => {
    if (error) {
        console.error(`Could not send request to API: ${error.message}`);
        return;
    }

    if (response.statusCode != 200) {
        console.error(`Expected status code 200 but received ${response.statusCode}.`);
        return;
    }

    console.log('Processing our list of movies');
    movies = JSON.parse(body);
    let movieList = '';
    movies.forEach(movie => {
        movieList += `${movie['title']}, ${movie['release_date']}\n`;
    });

    fs.writeFile('callbackMovies.csv', movieList, (error) => {
        if (error) {
            console.error(`Could not save the Ghibli movies to a file: ${error}`);
            return;
        }

        console.log('Saved our list of movies to callbackMovies.csv');;
    });
});

強調表示された変更に注目すると、fsモジュールをインポートしていることがわかります。 このモジュールは、すべてのNode.jsインストールで標準であり、ファイルに非同期で書き込むことができるwriteFile()メソッドが含まれています。

データをコンソールに記録する代わりに、データを文字列変数movieListに追加します。 次に、writeFile()を使用して、movieListの内容を新しいファイルcallbackMovies.csvに保存します。 最後に、writeFile()関数へのコールバックを提供します。この関数には、errorという1つの引数があります。 これにより、ファイルに書き込めない場合、たとえばnodeプロセスを実行しているユーザーにそれらの権限がない場合に対処できます。

ファイルを保存し、次のコマンドを使用してこのNode.jsプログラムをもう一度実行します。

node callbackMovies.js

ghibliMoviesフォルダーに、次の内容のcallbackMovies.csvが表示されます。

callbackMovies.csv

Castle in the Sky, 1986
Grave of the Fireflies, 1988
My Neighbor Totoro, 1988
Kiki's Delivery Service, 1989
Only Yesterday, 1991
Porco Rosso, 1992
Pom Poko, 1994
Whisper of the Heart, 1995
Princess Mononoke, 1997
My Neighbors the Yamadas, 1999
Spirited Away, 2001
The Cat Returns, 2002
Howl's Moving Castle, 2004
Tales from Earthsea, 2006
Ponyo, 2008
Arrietty, 2010
From Up on Poppy Hill, 2011
The Wind Rises, 2013
The Tale of the Princess Kaguya, 2013
When Marnie Was There, 2014

HTTPリクエストのコールバックでCSVファイルに書き込むことに注意することが重要です。 コードがコールバック関数に入ると、HTTPリクエストが完了した後にのみファイルに書き込みます。 CSVファイルを書き込んだ後でデータベースと通信したい場合は、writeFile()のコールバックで呼び出される別の非同期関数を作成します。 非同期コードが多いほど、より多くのコールバック関数をネストする必要があります。

5つの非同期操作を実行したいとします。各操作は、別の操作が完了したときにのみ実行できます。 これをコーディングすると、次のようになります。

doSomething1(() => {
    doSomething2(() => {
        doSomething3(() => {
            doSomething4(() => {
                doSomething5(() => {
                    // final action
                });
            });
        }); 
    });
});

ネストされたコールバックに実行するコードが多数ある場合、それらは大幅に複雑になり、読み取り不能になります。 JavaScriptプロジェクトのサイズと複雑さが増すにつれて、この効果は、最終的には管理できなくなるまで、より顕著になります。 このため、開発者は非同期操作を処理するためにコールバックを使用しなくなりました。 非同期コードの構文を改善するために、代わりにpromiseを使用できます。

簡潔な非同期プログラミングのためのPromiseの使用

promise は、将来のある時点で値を返すJavaScriptオブジェクトです。 非同期関数は、具象値の代わりにpromiseオブジェクトを返すことができます。 将来価値が出れば、約束は果たされたと言えます。 将来エラーが発生した場合は、約束が拒否されたと言います。 それ以外の場合、Promiseは保留状態で作業中です。

Promiseは通常、次の形式を取ります。

promiseFunction()
    .then([ Callback Function for Fulfilled Promise ])
    .catch([ Callback Function for Rejected Promise ])

このテンプレートに示されているように、promiseはコールバック関数も使用します。 then()メソッドのコールバック関数があります。これは、promiseが実行されたときに実行されます。 catch()メソッドのコールバック関数もあり、promiseの実行中に発生したエラーを処理します。

代わりにプロミスを使用するようにスタジオジブリプログラムを書き直して、プロミスを直接体験してみましょう。

Axios はJavaScript用のPromiseベースのHTTPクライアントなので、先に進んでインストールしましょう。

npm i axios --save

次に、選択したテキストエディタを使用して、新しいファイルpromiseMovies.jsを作成します。

nano promiseMovies.js

このプログラムは、axiosを使用してHTTPリクエストを作成し、fsの特別なpromisedベースのバージョンを使用して新しいCSVファイルに保存します。

promiseMovies.jsに次のコードを入力して、Axiosをロードし、ムービーAPIにHTTPリクエストを送信できるようにします。

promiseMovies.js

const axios = require('axios');

axios.get('https://ghibliapi.herokuapp.com/films');

最初の行では、axiosモジュールをロードし、返された関数をaxiosという定数に格納します。 次に、axios.get()メソッドを使用して、APIにHTTPリクエストを送信します。

axios.get()メソッドはpromiseを返します。 ジブリ映画のリストをコンソールに印刷できるように、その約束を連鎖させましょう。

promiseMovies.js

const axios = require('axios');
const fs = require('fs').promises;


axios.get('https://ghibliapi.herokuapp.com/films')
    .then((response) => {
        console.log('Successfully retrieved our list of movies');
        response.data.forEach(movie => {
            console.log(`${movie['title']}, ${movie['release_date']}`);
        });
    })

何が起こっているのかを分析してみましょう。 axios.get()を使用してHTTPGETリクエストを行った後、then()関数を使用します。この関数は、promiseが実行された場合にのみ実行されます。 この場合、コールバックの例で行ったように、映画を画面に印刷します。

このプログラムを改善するには、強調表示されたコードを追加して、HTTPデータをファイルに書き込みます。

promiseMovies.js

const axios = require('axios');
const fs = require('fs').promises;


axios.get('https://ghibliapi.herokuapp.com/films')
    .then((response) => {
        console.log('Successfully retrieved our list of movies');
        let movieList = '';
        response.data.forEach(movie => {
            movieList += `${movie['title']}, ${movie['release_date']}\n`;
        });

        return fs.writeFile('promiseMovies.csv', movieList);
    })
    .then(() => {
        console.log('Saved our list of movies to promiseMovies.csv');
    })

さらに、fsモジュールをもう一度インポートします。 fsのインポート後、.promisesがあることに注意してください。 Node.jsには、コールバックベースのfsライブラリのpromisedベースのバージョンが含まれているため、レガシープロジェクトで下位互換性が損なわれることはありません。

HTTPリクエストを処理する最初のthen()関数は、コンソールに出力する代わりにfs.writeFile()を呼び出すようになりました。 fsのpromiseベースのバージョンをインポートしたため、writeFile()関数は別のpromiseを返します。 そのため、writeFile()の約束が満たされた場合に備えて、別のthen()関数を追加します。

約束は新しい約束を返すことができ、約束を次々に実行することができます。 これにより、複数の非同期操作を実行するための道が開かれます。 これはpromisechaining と呼ばれ、コールバックのネストに似ています。 2番目のthen()は、ファイルへの書き込みに成功した後にのみ呼び出されます。

注:この例では、コールバックの例のようにHTTPステータスコードをチェックしませんでした。 デフォルトでは、axiosは、エラーを示すステータスコードを取得した場合、その約束を果たしません。 そのため、検証する必要はありません。


このプログラムを完了するには、次のように強調表示されているように、catch()関数を使用してpromiseをチェーンします。

promiseMovies.js

const axios = require('axios');
const fs = require('fs').promises;


axios.get('https://ghibliapi.herokuapp.com/films')
    .then((response) => {
        console.log('Successfully retrieved our list of movies');
        let movieList = '';
        response.data.forEach(movie => {
            movieList += `${movie['title']}, ${movie['release_date']}\n`;
        });

        return fs.writeFile('promiseMovies.csv', movieList);
    })
    .then(() => {
        console.log('Saved our list of movies to promiseMovies.csv');
    })
    .catch((error) => {
        console.error(`Could not save the Ghibli movies to a file: ${error}`);
    });

一連のpromiseでpromiseが実行されない場合、JavaScriptは、定義されていれば自動的にcatch()関数に移動します。 そのため、非同期操作が2つあるにもかかわらず、catch()句は1つしかありません。

次のコマンドを実行して、プログラムが同じ出力を生成することを確認しましょう。

node promiseMovies.js

ghibliMoviesフォルダーに、次のファイルを含むpromiseMovies.csvファイルが表示されます。

promiseMovies.csv

Castle in the Sky, 1986
Grave of the Fireflies, 1988
My Neighbor Totoro, 1988
Kiki's Delivery Service, 1989
Only Yesterday, 1991
Porco Rosso, 1992
Pom Poko, 1994
Whisper of the Heart, 1995
Princess Mononoke, 1997
My Neighbors the Yamadas, 1999
Spirited Away, 2001
The Cat Returns, 2002
Howl's Moving Castle, 2004
Tales from Earthsea, 2006
Ponyo, 2008
Arrietty, 2010
From Up on Poppy Hill, 2011
The Wind Rises, 2013
The Tale of the Princess Kaguya, 2013
When Marnie Was There, 2014

promiseを使用すると、コールバックのみを使用するよりもはるかに簡潔なコードを記述できます。 コールバックのpromiseチェーンは、コールバックをネストするよりもクリーンなオプションです。 ただし、非同期呼び出しを増やすと、Promiseチェーンが長くなり、維持が難しくなります。

コールバックとpromiseの冗長性は、非同期タスクの結果が得られたときに関数を作成する必要があることに起因します。 より良い経験は、非同期の結果を待って、それを関数の外の変数に入れることです。 そうすれば、関数を作成しなくても、変数の結果を使用できます。 これは、asyncおよびawaitキーワードを使用して実現できます。

async /awaitを使用してJavaScriptを作成する

async / awaitキーワードは、promiseを操作するときに代替構文を提供します。 then()メソッドでpromiseの結果を使用できるようにする代わりに、他の関数と同様に結果が値として返されます。 asyncキーワードを使用して関数を定義し、それがpromiseを返す非同期関数であることをJavaScriptに通知します。 awaitキーワードを使用して、約束が履行されたときに約束自体を返すのではなく、約束の結果を返すようにJavaScriptに指示します。

一般に、async /awaitの使用法は次のようになります。

async function() {
    await [Asynchronous Action]
}

async /awaitを使用するとスタジオジブリプログラムがどのように改善されるか見てみましょう。 テキストエディタを使用して、新しいファイルasyncAwaitMovies.jsを作成して開きます。

nano asyncAwaitMovies.js

新しく開いたJavaScriptファイルで、promiseの例で使用したものと同じモジュールをインポートすることから始めましょう。

asyncAwaitMovies.js

const axios = require('axios');
const fs = require('fs').promises;

async / awaitはpromiseを使用するため、インポートはpromiseMovies.jsと同じです。

次に、asyncキーワードを使用して、非同期コードで関数を作成します。

asyncAwaitMovies.js

const axios = require('axios');
const fs = require('fs').promises;

async function saveMovies() {}

saveMovies()という新しい関数を作成しますが、その定義の先頭にasyncを含めます。 非同期関数ではawaitキーワードしか使用できないため、これは重要です。

awaitキーワードを使用して、GhibliAPIから映画のリストを取得するHTTPリクエストを作成します。

asyncAwaitMovies.js

const axios = require('axios');
const fs = require('fs').promises;

async function saveMovies() {
    let response = await axios.get('https://ghibliapi.herokuapp.com/films');
    let movieList = '';
    response.data.forEach(movie => {
        movieList += `${movie['title']}, ${movie['release_date']}\n`;
    });
}

saveMovies()関数では、以前と同様にaxios.get()を使用してHTTPリクエストを作成します。 今回は、then()関数との連鎖は行いません。 代わりに、呼び出される前にawaitを追加します。 JavaScriptがawaitを検出すると、axios.get()が実行を終了し、response変数を設定した後にのみ、関数の残りのコードを実行します。 もう1つのコードは、ファイルに書き込めるようにムービーデータを保存します。

映画のデータをファイルに書き込みましょう。

asyncAwaitMovies.js

const axios = require('axios');
const fs = require('fs').promises;

async function saveMovies() {
    let response = await axios.get('https://ghibliapi.herokuapp.com/films');
    let movieList = '';
    response.data.forEach(movie => {
        movieList += `${movie['title']}, ${movie['release_date']}\n`;
    });
    await fs.writeFile('asyncAwaitMovies.csv', movieList);
}

fs.writeFile()を使用してファイルに書き込む場合も、awaitキーワードを使用します。

この関数を完了するには、promiseがスローする可能性のあるエラーをキャッチする必要があります。 これを行うには、コードをtry /catchブロックにカプセル化します。

asyncAwaitMovies.js

const axios = require('axios');
const fs = require('fs').promises;

async function saveMovies() {
    try {
        let response = await axios.get('https://ghibliapi.herokuapp.com/films');
        let movieList = '';
        response.data.forEach(movie => {
            movieList += `${movie['title']}, ${movie['release_date']}\n`;
        });
        await fs.writeFile('asyncAwaitMovies.csv', movieList);
    } catch (error) {
        console.error(`Could not save the Ghibli movies to a file: ${error}`);
    }
}

promiseは失敗する可能性があるため、非同期コードをtry /catch句で囲みます。 これにより、HTTPリクエストまたはファイル書き込み操作のいずれかが失敗したときにスローされるエラーがキャプチャされます。

最後に、非同期関数saveMovies()を呼び出して、nodeでプログラムを実行したときに実行されるようにします。

asyncAwaitMovies.js

const axios = require('axios');
const fs = require('fs').promises;

async function saveMovies() {
    try {
        let response = await axios.get('https://ghibliapi.herokuapp.com/films');
        let movieList = '';
        response.data.forEach(movie => {
            movieList += `${movie['title']}, ${movie['release_date']}\n`;
        });
        await fs.writeFile('asyncAwaitMovies.csv', movieList);
    } catch (error) {
        console.error(`Could not save the Ghibli movies to a file: ${error}`);
    }
}

saveMovies();

一見すると、これは典型的な同期JavaScriptコードブロックのように見えます。 渡される関数が少なく、少しきれいに見えます。 これらの小さな調整により、async /awaitを使用した非同期コードの保守が容易になります。

端末にこれを入力して、プログラムのこの反復をテストします。

node asyncAwaitMovies.js

ghibliMoviesフォルダーに、次の内容の新しいasyncAwaitMovies.csvファイルが作成されます。

asyncAwaitMovies.csv

Castle in the Sky, 1986
Grave of the Fireflies, 1988
My Neighbor Totoro, 1988
Kiki's Delivery Service, 1989
Only Yesterday, 1991
Porco Rosso, 1992
Pom Poko, 1994
Whisper of the Heart, 1995
Princess Mononoke, 1997
My Neighbors the Yamadas, 1999
Spirited Away, 2001
The Cat Returns, 2002
Howl's Moving Castle, 2004
Tales from Earthsea, 2006
Ponyo, 2008
Arrietty, 2010
From Up on Poppy Hill, 2011
The Wind Rises, 2013
The Tale of the Princess Kaguya, 2013
When Marnie Was There, 2014

これで、JavaScript機能async /awaitを使用して非同期コードを管理できるようになりました。

結論

このチュートリアルでは、JavaScriptが関数の実行とイベントループを使用した非同期操作の管理をどのように処理するかを学びました。 次に、さまざまな非同期プログラミング手法を使用して映画データのHTTP要求を行った後、CSVファイルを作成するプログラムを作成しました。 まず、廃止されたコールバックベースのアプローチを使用しました。 次に、promiseを使用し、最後にasync / awaitを使用して、promiseの構文をより簡潔にしました。

Node.jsを使用した非同期コードを理解すると、API呼び出しに依存するプログラムなど、非同期プログラミングの恩恵を受けるプログラムを開発できるようになります。 パブリックAPIのこのリストをご覧ください。 それらを使用するには、このチュートリアルで行ったように非同期HTTPリクエストを作成する必要があります。 さらに詳しく調べるには、これらのAPIを使用して、ここで学習した手法を実践するアプリを作成してみてください。