async/awaitとは
ES2015ではPromiseオブジェクトが導入され、非同期処理を簡潔に扱えるようになりました。
Promiseが導入される前は、非同期処理を扱うためにはコールバック処理を書いていました。例えば、「ex1.jsのファイルの読み込みを開始する ⇒ 読み込み完了 ⇒ ex2.jsのファイルの読み込みを開始する ⇒ 読み込み完了 ⇒ ...」といった流れで、直前のアクションが終了したら次のアクションを実行します。しかし、これには「コールバック地獄」の問題があり、その解決策としてPromiseオブジェクトが導入されたという経緯があります。
しかし、このPromiseオブジェクトは直感的に扱うのが難しいという問題がありました。
ES2017では、これを解決するためにasyncとawaitという文法が追加され、非同期処理がより簡潔に記述できるようになりました。asyncとawaitはいわゆる「シンタックスシュガー」で裏側ではPromiseオブジェクトを使用して機能が実現されています。
ちなみにasyncはasynchronousの略で「非同期」という意味です。awaitは文字通り「待つ」という意味です。
async
asyncはfunction(アロー関数)の前に記述します。asyncが記述された関数は同期関数から非同期関数に変化します。
関数は常にPromiseを返します。コード中にPromiseを返さないreturnがある場合、JavaScriptは自動的にその値を解決(resolve)されたPromiseにラップします。
つまり、次の全てのコードは同じ振る舞いをします。
async function func() {
return 1;
}
func().then((num) => {
console.log(num); // 1
});
function func() {
return Promise.resolve(1);
}
func().then((num) => {
console.log(num); // 1
});
function func() {
return new Promise((resolve, reject) => {
resolve(1);
});
}
func().then((num) => {
console.log(num); // 1
});
エラー処理
解決した場合のresolve(1)
をreturn 1
と書くことができますが、拒否の場合はreject(new Error("失敗!"))
をthrow new Error("失敗!")
と書くことができます。
つまり、次の全てのコードは同じ振る舞いをします。
async function func() {
throw new Error("失敗!");
}
func().catch((err) => {
console.log(err); // Error: 失敗! ...
});
async function func() {
//Promise.reject()には時間がかかるのでawaitしています
await Promise.reject(new Error("失敗!"));
}
func().catch((err) => {
console.log(err); // Error: 失敗! ...
});
async function func() {
return new Promise((resolve, reject) => {
reject(new Error("失敗!"));
});
}
func().catch((err) => {
console.log(err); // Error: 失敗! ...
});
エラーはメソッドチェーンの.catch(...)
以外にもtry..catchでキャッチすることができます。
async function func() {
try {
throw new Error("失敗!");
} catch(err) {
console.log(err); // Error: 失敗! ...
}
}
func();
具体的な使い分けとしては、コードの最上位にいるときはasyncが使えないため、then..catchで処理します。メソッドの中にいるときはasyncとawaitが使えるのでtry..catchで処理します。
await
awaitは、asyncが付いた関数の中でのみ有効です。これは非同期関数の実行を一時停止し、Promiseが解決するまで待機させます。
async function func() {
let promise = new Promise((resolve, reject) => {
setTimeout(() => resolve("done!"), 2000)
});
let result = await promise; // Promiseが解決するまで待機します
console.log(result); // "done!"
}
func();
async/awaitで非同期処理を書いてみる
Fetch APIを使用したjsonの取得処理をasync/awaitで書いてみます。
まず初めにこの処理をPromiseで書く場合には次のようになります。これを後でasync/awaitに書き換えます。
fetch()
はPromiseを返します。
function loadJson(url) {
return fetch(url).then(response => {
if (response.status == 200) {
return response.json();
} else {
throw new Error(response.status);
}
});
}
const url = 'fetch_async_await.json';
loadJson(url).catch(console.log);
Fetch APIを使用するための準備を行います。今回はNode.jsでWebサーバを用意します。
次のコマンドでhttp-serverをグローバルインストールしておいてください。
npm install -g http-server
適当なディレクトリを作成し、その中に次のファイルを作成してください。
fetch_async_await.json
{ "name": "chappie", "age": 6 }
fetch_async_await.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<script src="fetch_async_await.js"></script>
</body>
</html>
fetch_async_await.js
async function loadJson(url) {
const response = await fetch(url);
if (response.status === 200) {
// response.json()もPromiseを使用しているため、awaitします。
const json = await response.json();
return json;
}
throw new Error(response.status);
}
const url = 'fetch_async_await.json';
loadJson(url).catch(console.log);
ターミナル(コマンドプロンプト)でファイルを作成したディレクトリの中に移動し、http-server
を実行してください。その後、ブラウザでhttp://localhost:8080/fetch_async_await.html
にアクセスして動作を確認してください。
複数のPromiseを実行する
複数のPromiseを実行し、すべてのPromiseがresolve()
を実行したら何らかの処理を行いたいということは結構あるかと思います。複数のPromiseを実行するにはPromise.allを使用します。
今回はfetch()
を使用しますが、これも内部ではreturn new Promise(){...}
を行っており、resolve()
とreject()
を使用するため、asyncで処理することができます。複数のPromiseを待つ必要があるときはPromise.allでラップしてからawaitします。
最初に失敗したPromiseからの例外はPromise.allに伝播するので、try..catchで拾うことができます。
async function main() {
try {
let results = await Promise.all([
fetch("http:// ..."),
fetch("http:// ..."),
...
]);
} catch(err) {
console.log(err);
}
}
main();
async/awaitで複数のPromiseを実行する
Fetch APIとasync/awaitを使用してJSONファイルを複数取得する処理を書いてみます。今回もPromise.all()
を使用します。
今回はNode.jsのhttp-serverを使用するので、npmから以下のコマンドでインストールしておいてください。
npm install -g http-server
適当なディレクトリを作成し、その中に次のファイルを作成してください。
promise_all_1.json
{ "name": "Banana", "value": 150 }
promise_all_2.json
{ "name": "orange", "value": 100 }
promise_all.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<script src="promise_all.js"></script>
</body>
</html>
promise_all.js
async function getAllJson(urls) {
const results = Promise.all(
urls.map(async url => {
try {
const response = await fetch(url);
if(response.status === 200) {
const json = response.json();
return json;
}
throw new Error(response.status);
} catch(err) {
console.log(err);
}
})
);
return results;
}
const urls = [
"promise_all_1.json",
"promise_all_2.json",
];
getAllJson(urls).then(feeds => {
feeds.forEach(feed => {
console.log(feed);
});
});
ターミナル(コマンドプロンプト)でファイルを作成したディレクトリの中に移動し、http-server
を実行してください。その後、ブラウザでhttp://localhost:8080/promise_all.html
にアクセスして動作を確認してください。
補足
以下のように書くこともできます。
async function main() {
try {
const promiseResults = await Promise.all([
fetch("promise_all_1.json"),
fetch("promise_all_2.json"),
]);
const results = await Promise.all(
promiseResults.map(pr => pr.json())
);
results.forEach(feed => {
console.log(feed);
});
} catch (err) {
console.log(err);
}
}
main();