JavaScript async/await

概要: このチュートリアルでは、JavaScriptのasync / awaitキーワードを使用して非同期コードを作成する方法を学びます。

async / await の仕組みを理解するには、Promise の仕組みを知っておく必要があることに注意してください。

JavaScript async / await キーワード入門

以前は、非同期操作を処理するためにコールバック関数を使用していました。しかし、多くのコールバック関数をネストすると、コードの保守が難しくなり、コールバック地獄として知られる悪名高い問題が発生する可能性があります。

次の順序で3つの非同期操作を実行する必要があると仮定します。

  1. データベースからユーザーを選択します。
  2. APIからユーザーのサービスを取得します。
  3. サーバーからのサービスに基づいてサービスコストを計算します。

次の関数は、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 300Code 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 promisethrowステートメントがあるかのようにエラーをスローします。

次のコード

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キーワードを使用して、同期コードのように見える非同期コードを記述する方法を学びました。

このチュートリアルは役に立ちましたか?