JavaScriptの非同期処理を行うPromiseとAsyncについて学ぶ

5 min読了の目安(約5100字TECH技術記事

はじめに

この記事はJavaScriptのPromiseとAsyncについて学んだことを備忘録として残すことを目的に書きます。

この記事のサンプルのコードではブラウザ上で非同期処理を行うFetch APIを使用して、Board APIを叩きます。
JavaScript Primer 非同期処理を大変参考にさせていただきました。

この記事が他の人の参考になれば幸いです。
また、この記事の内容に間違った記載がありましたら、指摘していただけるとありがたいです。

環境

2021年1月10日時点。

名前 バージョン
macOS Big Sur 11.1

Promiseとは

PromiseはES2015で導入された非同期処理の結果を表現するビルトインオブジェクトです。非同期処理を統一されたインターフェースで扱うことができます。

Promiseに対応した非同期処理を行う関数はPromiseインスタンスを戻り値として返します。このPromiseインスタンスのthenメソッドに成功時の処理を行うコールバック関数を渡し、catchメソッドに失敗時の処理を行うコールバック関数を渡します。

// Promiseインスタンスを返す
const result = fetch('https://www.boredapi.com/api/activity/');
console.log(result);  // -> Promise {<pending>}

// 成功時と失敗時の処理をコールバック関数としてthen、catchメソッドに渡し、実行する
result
    .then(response => response.json())
    .then(data => console.log(data))
    .catch(error => console.log(error))
// -> {...}

Promiseインスタンスを作成する

PromiseインスタンスはPromiseのコンストラクタ関数で作成します。コンストラクタ関数はexecutorと呼ばれる関数を1つ引数に取ります。executorはresolverejectと呼ばれる2つの関数を引数に取り、非同期処理が成功した場合はresolve関数を呼び、失敗した場合はreject関数を呼び出します。

const ayncFunction = () => {
    const isSuccessful = true;
    return new Promise((resolve, reject) => {
        // setTimeout関数で1000ミリ秒後に非同期で処理する
        setTimeout(() => {
            if (isSuccessful) {
                // 成功時にresolve関数を呼び出す。
                resolve('The process was successful.');
            } else {
                // 失敗時にreject関数を呼び出す。
                reject(new Error("An error has occurred."));
            }
        }, 1000);
    });
}

ayncFunction()
    .then((response) => {
        console.log(response);  // -> The process was successful.
    })
    .catch((error) => {
        console.log(error);  // isSuccessfulをfalseにした時 An error has occurred.
    });

このように見るとthenメソッドはresolve関数を、catchメソッドはreject関数をコールバック関数として渡し、登録しているだけであることが分かります。
thenメソッドは以下のようにresolve関数とreject関数の両方を登録することができます。ですが、失敗時の処理を行うコールバック関数はcatchメソッドで渡した方が分かりやすいでしょう。

const ayncFunction = () => {
    const isSuccessful = false;
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            if (isSuccessful) {
                resolve('The process was successful.');
            } else {
                reject(new Error("An error has occurred."));
            }
        }, 1000);
    });
}

ayncFunction()
    .then((response) => {
            console.log(response);
        },
        (error) => {
            console.log(error.message);  // -> An error has occurred.
        });

then、catch、finallyを用いたメソッドチェーン

thencatchメソッドは常に新しいPromiseインスタンスを作成して返します。このためPromiseインスタンスのthenメソッドにさらにthenメソッドを実行したり、catchメソッドを実行できます。また、thenメソッドやcatchメソッドに渡したコールバック関数の戻り値は次のコールバック関数の引数として渡せます。コールバック関数の戻り値としてPromiseインスタンスを返した場合は例外的にthencatchメソッドの戻り値となります。
finallyメソッドを使用すると成功時でも失敗時でも呼び出されます。

const promise = new Promise((resolve) => {
    resolve(1);
});
promise.then((value => {
    console.log(value);  // -> 1
    return value * 2;
})).then((value => {
    console.log(value);  // -> 2
    return value * 2;
})).then((value => {
    console.log(value);  // -> 4
})).then((value => {
    console.log(value);  // -> undefined 前のコールバック関数が何も返さなかったため
}));

また、thenメソッドなどは以下のように非同期的なタイミングで実行されます。

const promise = new Promise((resolve) => {
    console.log('1. コンストラクタ関数の実行');
    resolve();
});
promise.then(() => {
    console.log('3. コールバック関数の実行');
});
console.log('2. 同期処理の実行');

Async Function

Async FunctionはES2017で導入された非同期処理を行う関数を定義する構文です。必ず戻り値としてPromiseインスタンスを返す関数を定義します。また、Async Function内ではawait式を利用することができます。以下のように書きます。

const asyncFunction = async () => {
    return "The process was successful.";
};

asyncFunction().then(value => {
    console.log(value);  // -> The process was successful.
});

Async Functionの戻り値のPromiseインスタンスは状況によって変わります。値をreturnした場合はthenメソッドで処理できるPromiseインスタンス、Promiseインスタンスをreturnした場合はそのままのPromiseインスタンス、Async Function内で例外が発生した場合はそのエラーを持ち、catchメソッドで処理できるPromiseインスタンスが返されます。

await式

Async Function内ではawait式を利用できます。await式は右のPromiseインスタンスが実行されるまでその場で非同期処理の完了を待ちます。await式の戻り値はresolveされた値、もしくはrejectが実行された場合はawait式でエラーが投げられます。このエラーはcatchメソッドで処理できます。

const promise = new Promise((resolve, reject) => {
    resolve('The process was successful.');
});

const aysncFunction = async () => {
    const value = await promise;  // resolveされた値がvalueに代入される
    console.log(value);  // -> The process was successful.
};

aysncFunction();
const promise = new Promise((resolve, reject) => {
    reject(new Error('An error has occurred.'));
});

const aysncFunction = async () => {
    const value = await promise;  // 例外の発生
    console.log(value);  // -> 実行されない
};

aysncFunction().catch(error => {
    console.log(error);
});

最初のPromiseインスタンスとthenメソッドを使って書いたコード例をawait式を使うと以下のようになります。

const fetchJson = async () => {
    const response = await fetch('https://www.boredapi.com/api/activity/');
    return await response.json()
};

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

参考