JavaScript
 Computer >> コンピューター >  >> プログラミング >> JavaScript

JavaScriptのasync/awaitとは?Promiseとの違いや使い方を実例付きで解説

ES6(ES2015)で導入されたPromiseは、コールバック関数を多用することなく非同期処理を簡潔に記述できる仕組みです。Promiseを使えば、書きにくく保守もしにくい多段階のコールバック(いわゆる「コールバック地獄」)に悩まされることはありません。

さらに、Promiseによる非同期処理をもっと簡単に書けるようにする機能が「async/await」です。async/awaitを使うと、同期処理のように見えるコードで、実際には非同期の処理を実行できます。

この記事では、async/awaitの基本概念と、実際のコードでの使い方について詳しく解説します。それでは始めましょう!

Promiseのおさらい

async/awaitの説明に入る前に、まずはPromiseについておさらいしておきましょう。Promiseは非同期処理の結果を表すオブジェクトです。「これからある処理を実行する」こと、そして処理が成功すれば値が返され、失敗すればエラーが返されることをコードに伝えます。

Promiseは、作成された時点ではまだ確定していない値を表します。つまりPromiseとは、「将来、値がコードに送り返される」という約束そのものなのです。なお、Promiseはオブジェクトであるため、先頭は大文字で表記します。

具体例を挙げてみましょう。APIからリソースを取得する場合、リクエストの処理には数秒かかることがあります。ユーザーを待たせる代わりに、その処理をPromiseに任せれば、プログラムの残りの部分は処理を続けられます。

つまり、データを取得している間もWebサイトのUIの一部を描画し続けることができるのです。値が返された瞬間に、Promiseがその値をメインプログラムへ渡します。

以下はPromiseの例です。

let sendCookies = new Promise(resolve => {
	setTimeout(() => {
		resolve("Your cookies have been sent!");
	}, 1000);
});

sendCookies.then(data => {
	console.log(data);
})

このコードを実行すると「Your cookies have been sent!」と出力されます。sendCookies.then()メソッドを実行するとPromiseが開始され、プログラムは1,000ミリ秒待機した後、値「Your cookies have been sent!」をメインプログラムに返します。

asyncとawaitの基本的な使い方

async/awaitでは、await文があると、そのasync関数内の処理はPromiseが返されるまで一時停止します。このため開発者の間ではよく「async/awaitは見た目は同期処理だが、実際には非同期タスクを実行する」と言われています。

次の例を見てみましょう。

function sendCookies() {
	return new Promise(resolve => {
		setTimeout(() => {
			resolve("Your cookies have been sent!");
		}, 1000);
	});
}

async function main() {
	const sendMessage = await sendCookies();
	console.log(sendMessage);
}

main();

このコードも「Your cookies have been sent!」を出力します。sendCookies()関数が値を返すまでに1,000ミリ秒かかります。ここではasync関数を宣言しているため、コードはPromiseが解決(resolve)または拒否(reject)されるのを待ちます。

「async」キーワードは、関数内で非同期処理を行いたいことを示します。「await」キーワードは、sendCookies()のPromiseが返されるまでプログラムの実行を待機させる役割を持ちます。

なお、async関数は必ずPromiseを返します。

複数のステップでasync/awaitを使う

async/awaitが最も活躍するのは、複数のPromiseを扱う場合です。これは「Promiseチェーン」と呼ばれることもあります。各ステップのPromiseが返されるのを待ってから次のステップに進むためです。

function processOrder() {
	return new Promise(resolve => {
		setTimeout(() => {
			resolve("Your cookie order is being processed...");
		}, 1000);
	});
}

function sendCookies() {
	return new Promise(resolve => {
		setTimeout(() => {
			resolve("Your cookies have been sent!");
		}, 1000);
	});
}

async function main() {
	const processingMessage = await processOrder();
	console.log(processingMessage);

	const sendMessage = await sendCookies();
	console.log(sendMessage);
}

このコードの出力は次のとおりです。

Your cookie order is being processed...
Your cookies have been sent!

各ステップの完了には1,000ミリ秒かかります。processOrder()のPromiseが返されるまで、sendCookies()関数は実行されません。

async関数の3つの宣言方法

async/await関数の使い方は主に3通りあります。

1つ目は、これまでの例で示した方法です。Promiseを返す関数を宣言し、「async」と「await」キーワードを使ってその関数を実行します。

2つ目は、アロー関数でasync関数を宣言する方法です。

const main = async () => {
	const sendMessage = await sendCookies();
	console.log(sendMessage);
}

このコードも「Your cookies have been sent!」を出力します。最初の例と同じですが、main()関数を宣言する代わりにアロー関数を使用しています。

3つ目は、関数式(function式)の構文を使う方法です。

const main = async function() {
	const sendMessage = await sendCookies();
	console.log(sendMessage);
}

このコードの出力も同じく「Your cookies have been sent!」です。違いは関数の宣言方法だけです。実際、この構文は前の例と非常によく似ており、アロー関数の代わりに「function()」キーワードを使っている点だけが異なります。

どの宣言方法が最適という決まりはありません。書いているプログラムや好みの構文によって使い分ければよいでしょう。アロー関数が最も簡潔という意見もありますが、状況によっては他の宣言方法の方が適していることもあります。

fetch()を使ったWebリクエストの処理

async/awaitの最も一般的な用途のひとつが、fetch()のようなPromiseベースのAPIを使ったWebリクエストの処理です。

次の例を見てみましょう。

async function retrieveComments() {
	const res = await fetch('https://jsonplaceholder.typicode.com/comments');
	var comments = await res.json();

	comments = comments.map(comment => comment.name);

	console.log(comments);
}

retrieveComments();

このコードの出力は次のようになります。

["id labore ex et quam laborum", "quo vero reiciendis velit similique earum" ...]

retrieveComments()関数を実行すると、「await」キーワードによってfetch()文の完了を待ちます。つまり、Webリクエストが処理されるまでプログラムの残りの部分は先に進みません。これはfetch()関数がPromiseを返すためです。

fetch()からPromiseが返されたら、その戻り値をJSONに変換します。その後、オブジェクトをマップ処理してすべてのコメント名のリストを取得し、コンソールに出力しています。

エラーハンドリングの方法

コードにエラーはつきものです。開発者としては、エラーへの備えが必要です。非同期関数におけるエラー処理は、try...catch文を使って同期的に行えます。

まずは.catch()を使った例です。

async function retrieveCookies() {
	const res = await fetch('https://thisapidoesnotexist.app/cookies');
	var cookieNames = await res.json();

	cookieNames = cookieNames.map(cookie => cookie.name);

	resolve(cookieNames);
}

async function printCookies() {
	const cookies = await retrieveCookies();
	console.log(cookies);
}

printCookies().catch(res => console.log(res.message));

このコードの出力:

NetworkError when attempting to fetch resource.

コード自体は正しく動作しますが、アクセスしようとしているAPIが存在しないため、Promiseはデータを返すことができず、拒否(reject)されたPromiseを返します。

この例では.catch()文を使って、関数からエラーが返された場合にその内容を出力しています。

同様に、async関数はtry/catch文でもエラーを捕捉できます。

async function retrieveCookies() {
	const res = await fetch('https://thisapidoesnotexist.app/cookies');
	var cookieNames = await res.json();

	cookieNames = cookieNames.map(cookie => cookie.name);

	resolve(cookieNames);
}

async function printCookies() {
	try {
		const cookies = await retrieveCookies();
		console.log(cookies);
	} catch(error) {
		console.log(error);
	}
}

printCookies();

このコードの出力も「NetworkError when attempting to fetch resource.」です。この例ではtry/catch文を使って、awaitメソッドが成功したPromiseを返したかどうかを確認しています。ここでもAPIが無効なためPromiseは拒否され、catchブロックが実行されてエラーがコンソールに出力されます。

まとめ

async/awaitを使えば、コード内の非同期処理を簡潔に記述できます。async関数を宣言すると、「await」キーワードで処理の完了を待機できるようになります。awaitキーワードは、Promiseを返す関数と組み合わせて使う必要があります。

これであなたも、プロのようにJavaScriptのasync/awaitを使いこなせるはずです!

  1. 難読化されたJavaScriptをデバッグする方法|整形ツールで可読化する手順

    難読化(Obfuscation)や圧縮(Minify)されたJavaScriptコードは、変数名が短縮され、改行やインデントが失われているため、そのままでは人間が読んでデバッグするのが非常に困難です。こうしたコードを解析するには、JavaScriptフォーマッター(ビューティファイアー)を使って、可読性の高い形式へ整形(Beautify)するのが最も手軽な方法です。 オンラインツールでJavaScriptを整形する手順 1. 整形ツールにアクセスする まず、ブラウザで無料のオンライン整形ツール(例:Beautifier)を開きます。インストール不要ですぐに利用できます。 2. 難読化され

  2. JavaScriptでシンタックスハイライトを作成・使用する方法をわかりやすく解説

    JavaScriptでシンタックスハイライト(構文強調表示)を作成して使用するには、正規表現とDOM操作を組み合わせるのが一般的です。ここでは、ページ内のリンク(アンカータグ)を自動的に検出し、スタイルを適用して強調表示するサンプルコードを紹介します。 サンプルコード <!DOCTYPE html> <html> <head> <style>     body {         font-family: "Segoe