概要: このチュートリアルでは、JavaScriptのasync
/ await
キーワードを使用して非同期コードを作成する方法を学びます。
async
/ await
の仕組みを理解するには、Promise の仕組みを知っておく必要があることに注意してください。
JavaScript async / await キーワード入門
以前は、非同期操作を処理するためにコールバック関数を使用していました。しかし、多くのコールバック関数をネストすると、コードの保守が難しくなり、コールバック地獄として知られる悪名高い問題が発生する可能性があります。
次の順序で3つの非同期操作を実行する必要があると仮定します。
- データベースからユーザーを選択します。
- APIからユーザーのサービスを取得します。
- サーバーからのサービスに基づいてサービスコストを計算します。
次の関数は、3つのタスクを示しています。非同期操作をシミュレートするためにsetTimeout()
関数を使用していることに注意してください。
function getUser(userId, callback) {
console.log('Get user from the database.');
setTimeout(() => {
callback({
userId: userId,
username: 'john'
});
}, 1000);
}
function getServices(user, callback) {
console.log(`Get services of ${user.username} from the API.`);
setTimeout(() => {
callback(['Email', 'VPN', 'CDN']);
}, 2 * 1000);
}
function getServiceCost(services, callback) {
console.log(`Calculate service costs of ${services}.`);
setTimeout(() => {
callback(services.length * 100);
}, 3 * 1000);
}
Code language: JavaScript (javascript)
以下は、ネストされたコールバック関数を示しています。
getUser(100, (user) => {
getServices(user, (services) => {
getServiceCost(services, (cost) => {
console.log(`The service cost is ${cost}`);
});
});
});
Code language: JavaScript (javascript)
出力
Get user from the database.
Get services of john from the API.
Calculate service costs of Email,VPN,CDN.
The service cost is 300
Code language: JavaScript (javascript)
このコールバック地獄の問題を回避するために、ES6では、非同期コードをより管理しやすい方法で記述できるPromiseが導入されました。
まず、各関数でPromise
を返す必要があります。
function getUser(userId) {
return new Promise((resolve, reject) => {
console.log('Get user from the database.');
setTimeout(() => {
resolve({
userId: userId,
username: 'john'
});
}, 1000);
})
}
function getServices(user) {
return new Promise((resolve, reject) => {
console.log(`Get services of ${user.username} from the API.`);
setTimeout(() => {
resolve(['Email', 'VPN', 'CDN']);
}, 2 * 1000);
});
}
function getServiceCost(services) {
return new Promise((resolve, reject) => {
console.log(`Calculate service costs of ${services}.`);
setTimeout(() => {
resolve(services.length * 100);
}, 3 * 1000);
});
}
Code language: JavaScript (javascript)
次に、Promiseをチェーンします。
getUser(100)
.then(getServices)
.then(getServiceCost)
.then(console.log);
Code language: JavaScript (javascript)
ES2017では、Promiseの上に構築されたasync
/ await
キーワードが導入されました。これにより、より同期コードのように見える、より読みやすい非同期コードを記述できます。技術的には、async
/ await
はPromiseのシンタックスシュガーです。
関数がPromiseを返す場合は、次のように関数呼び出しの前にawait
キーワードを配置できます。
let result = await f();
Code language: JavaScript (javascript)
await
は、f()
から返されたPromise
が解決されるのを待ちます。await
キーワードは、async
関数内でのみ使用できます。
以下は、3つの非同期操作を順番に呼び出すasync
関数を定義しています。
async function showServiceCost() {
let user = await getUser(100);
let services = await getServices(user);
let cost = await getServiceCost(services);
console.log(`The service cost is ${cost}`);
}
showServiceCost();
Code language: JavaScript (javascript)
ご覧のとおり、非同期コードは同期コードのように見えます。
それでは、async / awaitキーワードを詳しく見ていきましょう。
asyncキーワード
async
キーワードを使用すると、非同期操作を処理する関数を定義できます。
async
関数を定義するには、次のように関数キーワードの前にasync
キーワードを配置します。
async function sayHi() {
return 'Hi';
}
Code language: JavaScript (javascript)
非同期関数は、イベントループを介して非同期に実行されます。常にPromise
を返します。
この例では、sayHi()
関数がPromise
を返すため、次のように使用できます。
sayHi().then(console.log);
Code language: JavaScript (javascript)
次のコードに示すように、sayHi()
関数から明示的にPromise
を返すこともできます。
async function sayHi() {
return Promise.resolve('Hi');
}
Code language: JavaScript (javascript)
効果は同じです。
通常の関数に加えて、関数式でasync
キーワードを使用できます。
let sayHi = async function () {
return 'Hi';
}
Code language: JavaScript (javascript)
let sayHi = async () => 'Hi';
Code language: JavaScript (javascript)
クラスのメソッド
class Greeter {
async sayHi() {
return 'Hi';
}
}
Code language: JavaScript (javascript)
awaitキーワード
await
キーワードは、Promise
が解決された状態または拒否された状態になるのを待つために使用します。await
キーワードは、async
関数内でのみ使用できます。
async function display() {
let result = await sayHi();
console.log(result);
}
Code language: JavaScript (javascript)
この例では、await
キーワードは、メッセージを表示する前にsayHi()
関数が完了するのを待つようにJavaScriptエンジンに指示します。
await
演算子をasync
関数の外で使用すると、エラーが発生することに注意してください。
エラー処理
Promiseが解決されると、await promise
は結果を返します。ただし、Promiseが拒否された場合、await promise
はthrow
ステートメントがあるかのようにエラーをスローします。
次のコード
async function getUser(userId) {
await Promise.reject(new Error('Invalid User Id'));
}
Code language: JavaScript (javascript)
...これは次のコードと同じです。
async function getUser(userId) {
throw new Error('Invalid User Id');
}
Code language: JavaScript (javascript)
実際のシナリオでは、Promiseがエラーをスローするまでに時間がかかります。
通常のthrow
ステートメントと同じように、try...catch
ステートメントを使用してエラーをキャッチできます。
async function getUser(userId) {
try {
const user = await Promise.reject(new Error('Invalid User Id'));
} catch(error) {
console.log(error);
}
}
Code language: JavaScript (javascript)
1つ以上のawait promise
によって引き起こされたエラーをキャッチすることが可能です。
async function showServiceCost() {
try {
let user = await getUser(100);
let services = await getServices(user);
let cost = await getServiceCost(services);
console.log(`The service cost is ${cost}`);
} catch(error) {
console.log(error);
}
}
Code language: JavaScript (javascript)
このチュートリアルでは、JavaScriptのasync
/ await
キーワードを使用して、同期コードのように見える非同期コードを記述する方法を学びました。