30
12

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

JavaScriptにおける順次実行をPromiseとasync/awaitでできました!

30
Last updated at Posted at 2018-12-11

概要

サーバーからデータを受け取るアプリを作成した際にJavaScriptの順次実行に悪戦苦闘したので、その際に調べたことを本記事にまとめておきたいと思います。


目次

  1. 同期処理と非同期処理
  2. 順次実行
  3. 順次実行とエラー処理
  4. 複雑な順次実行
  5. 最後に

同期処理と非同期処理

JavaScriptでは、基本的に上から順に1つずつ実行されます。上から順に1つずつ実行することを同期処理と言います。次の sample1.js では、①を実行した後に②を実行しているので、以下のようになります。

sample1.js
console.log("start");	// ①
console.log("end");		// ②
sample1.jsの実行結果
start
end

次の sample2.js では、APIからデータを取得しています。データをまとめて送るなどといった処理はすべてサーバ側で行われ、データが送られてきます。このように他の場所で実行してくれる処理を非同期処理と言います。(※ sample2.js 以降ではjQueryも使用しています。)

sample2.js
$.ajax({
	type: "GET",
	url: "APIのURL",
	data: "送信するデータ",
	dataType: "送信するデータ型",
	contentType: "受信するデータ型"
// 成功時の処理
}).done(function(response, textStatus, jqXHR){
	var message = "success";
	console.log(message);
});
sample2.jsの実行結果
success

それでは、同期処理と非同期処理を混ぜてみます。次の sample3.js はAPIからデータを取得し、その成否を表示しています。

sample3.js
main=()=>{
	console.log("START");			// ①
	var message = getData("info");	// ②
	console.log(message);			// ③
	console.log("END");				// ④
}

// APIからデータ取得
getData=(sendData)=>{
	console.log("getData begin");
	$.ajax({
		type: "GET",
		url: "APIのURL",
		data: sendData,
		dataType: "受信するデータ型",
		contentType: "送信するデータ型"
	// 成功時の処理
	}).done(function(response, textStatus, jqXHR){
		var message = "success";
		console.log("getData finish");
		return message;
	});
}
sample3.jsの予想結果
START
getData begin
getData finish
success
END

上から①、②、③、④の順に実行されると、上記の sample3.jsの予想結果 になります。しかし、非同期処理は実行を他の場所に任せている分、その間に次の処理を同時に行うことができ、並行処理のようになります。そのため、②でリクエストを送り、データを待つ間に、③や④を実行してしまいます。そのため、実際の実行結果は下記の sample3.jsの実行結果 になります。messageはgetDataが終わっていないため、何もないので、undefinedと表示されています。このように、非同期処理が終わるのを待たずに実行されてしまい、実行する順番が入れ替わってしまうことがあります。

sample3.jsの実行結果
START
getData begin
undefined
END
getData finish

上記の予想結果のようにするにはどうすればよいのでしょうか?

順次実行

非同期処理を上から順に実行するには、Promiseとasync/awaitを使用するのがおすすめです。 sample3.js をPromiseとasync/awaitを用いて書き直したものが次の sample4.js になります。Promiseとasync/awaitを使用することで、awaitがついている関数の処理が終わるまで待ってくれます。

  Promiseとasync/awaitの使い方:
    1. 非同期処理をgetData(sendData)のように1つの関数にまとめておきます
    2. 順次実行したい非同期処理や同期処理をasyncで囲みます(①)
    3. 順次実行したい非同期処理の関数の前にawaitを書きます(②)
    4. 関数にまとめた非同期処理をPromiseで囲みます(③)
    5. 非同期処理が成功した時に返したい値などをresolveに入れます(④)
     ※入れられるのは1つだけなので、複数ある場合は配列にまとめてください

sample4.js
main=()=>{
	(async()=>{									// ①
		console.log("START");
		var message = await getData("info");	// ②
		console.log(message);
		console.log("END");
	})();										// ①
}

// APIからデータ取得
getData=(sendData)=>{
	return new Promise((resolve, reject)=>{		// ③
		console.log("getData begin");
		$.ajax({
			type: "GET",
			url: "APIのURL",
			data: sendData,
			dataType: "受信するデータ型",
			contentType: "送信するデータ型"
		// 成功時の処理
		}).done(function(response, textStatus, jqXHR){
			var message = "success";
			console.log("getData finish");
			resolve(message);					// ④
		});
	});
}
sample4.jsの実行結果
START
getData begin
getData finish
success
END

順次実行とエラー処理

しかし、このままでは、エラー処理がされていません。 sample4.js にエラー処理を追加してみたものが次の sample5.js です。
エラー処理の場合、asyncに囲まれている部分でエラーが発生した場合に行われる処理をcatchの中に書きます(①)。また、非同期処理の関数では、APIからエラーが返ってきた際の処理をAjaxの場合はfailに書きます(②)。そして、非同期処理が失敗した時に返したい値をrejectに入れます(③)。このrejectに入れた中身がasyncのcatchのerrの中身になります。

sample5.js
main=()=>{
	(async()=>{
		console.log("START");
		var message = await getData("info");
		console.log(message);
		console.log("END");
	})().catch((err)=>{								// ①
		console.log(err);
		console.log("ERROR END");
	});
}

// APIからデータ取得
getData=(sendData)=>{
	return new Promise((resolve, reject)=>{
		console.log("getData begin");
		$.ajax({
			type: "GET",
			url: "APIのURL",
			data: sendData,
			dataType: "受信するデータ型",
			contentType: "送信するデータ型"
		// 成功時の処理
		}).done((response,textStatus,jqXHR)=>{
			var message = "success";
			console.log("getData finish");
			resolve(message);
		// 失敗時の処理
		}).fail((jqXHR, textStatus, errorThrown)=>{	// ②
			var message = "failed";
			console.log("getData finish");
			reject(message);						// ③
		});
	});
}
sample5.jsの実行結果(成功時)
START
getData begin
getData finish
success
END
sample5.jsの実行結果(失敗時)
START
getData begin
getData finish
failed
ERROR END

複雑な順次実行

実はPromiseの中にasyncを書き、入れ子構造にすることができます。そのため、非同期処理の繰り返しも順次実行することができます。その一例を sample6.js に示します。

sample6.js
main=()=>{
	(async()=>{
		console.log("START");
		var message = await repeat(["info", "name"]);
		console.log(message);
		console.log("END");
	})().catch((err)=>{
		console.log(err);
		console.log("ERROR END");
	});
}

// 繰り返し処理
repeat=(words)=>{
	return new Promise((resolve,reject)=>{
		(async()=>{
			console.log("repeat begin");
			var multiMessage = [];
			for (var i=0; i<words.length; i++) {
				var message = await getData(words[i]);
				multiMessage.push(message);
			}
			console.log("repeat finish");
			resolve(multiMessage);
		})().catch((err)=>{
			console.log("repeat error");
			reject(err);
		});
	})
}

// APIからデータ取得
getData=(sendData)=>{
	return new Promise((resolve, reject)=>{
		console.log("getData begin");
		$.ajax({
			type: "GET",
			url: "APIのURL",
			data: sendData,
			dataType: "受信するデータ型",
			contentType: "送信するデータ型"
		// 成功時の処理
		}).done((response,textStatus,jqXHR)=>{
			var message = "success";
			console.log("getData finish");
			resolve("success");
		// 失敗時の処理
		}).fail((jqXHR, textStatus, errorThrown)=>{
			var message = "failed";
			console.log("getData finish");
			reject(message);
		});
	});
}
sample6.jsの実行結果(成功時)
START
repeat begin
getData begin			// 1回目のgetData
getData finish
getData begin			// 2回目のgetData
getData finish
repeat finish
["success", "success"]	// 1回目のgetDataの結果と2回目のgetDataの結果
END

最後に

今回、社内イベントで初めて記事を書かせていただきました。拙いところが多々あるかと思いますが、改善点等ございましたら、教えていただけると幸いです。
最後まで読んでくださり、ありがとうございました。ヾ(。>﹏<。)ノ゙


参考文献

本記事を書くにあたり、参考にさせていただきました


2026年07月追記

社会人1年目の拙い記事を見ていただき、ありがとうございます!
今更ながら、長年放置していて大崩れしていたマークダウンを直しました…
ついでに、現在の主流な書き方も追記しておきます。

Fetch APIを用いた非同期処理の書き方

この記事を執筆した2018年当時は $.ajax (jQuery) や手動での new Promise 囲みが主流でしたが、現在のJavaScriptでは標準の Fetch API を使うことで、外部ライブラリを使わずにさらにシンプルに非同期処理を書くことができます。

// APIからデータ取得
const getData = async (sendData) => {
  try {
    // クエリパラメータの形式に変更する
    // 例:{mode: "view", redirect: true } → mode=view&redirect=true
    const params = new URLSearchParams(sendData);
    // awaitでサーバーからのレスポンスを待つ
    const response = await fetch(`APIのURL?${params}`);

    // 成功時の処理
    if (response.ok) {
        const data = await response.json();
        console.log("getData finish");
        return "success";
    }
    
    // 失敗時、エラーを投げてcatchでまとめて処理する
    throw new Error(`HTTP error! status: ${response.status}`);
  }
  // 失敗時の処理
  catch (error) {
    console.error("getData error:", error);
    return "failed";
  }
};

// APIを発火
const main = async () => {
  console.log("START");
  const message = await getData({ info: "test" });
  console.log(message);
  console.log("END");
};

main();

変更点

  1. jQueryが不要
    ブラウザ標準の fetch() だけで通信が完結します。
  2. 手動の new Promise が不要
    async を付けた関数は自動的にPromiseオブジェクトを返すため、コードの階層がさらに浅くスッキリします。
  3. エラー処理の統一
    try/catch 構文だけで、ネットワークエラーもHTTPステータスエラーも綺麗にキャッチできるようになりました。
30
12
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
30
12

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?