6時だョ!!全員集合!!

Rails・JavaScrictを中心にアウトプットします。

2022年2月24日 非同期処理:コールバック/Promise/Async - Promiseチェーンをawait式で表現する (JavaScript Primer)

Promiseチェーンをawait式で表現する

まずはawaitを使わない逐次的な処理から見てみます。

以下はPromiseチェーンで複数の非同期処理を逐次的に行っている例です。

function dummyFetch(path) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            if (path.startsWith("/resource")) {
                resolve({ body: `Response body of ${path}` });
            } else {
                reject(new Error("NOT FOUND"));
            }
        }, 1000 * Math.random());
    });
}
// リソースAとリソースBを順番に取得する
function fetchAB() {
    const results = [];
    return dummyFetch("/resource/A").then(response => {
        results.push(response.body);
        return dummyFetch("/resource/B");
    }).then(response => {
        results.push(response.body);
        return results;
    });
}
// リソースを取得して出力する
fetchAB().then((results) => {
    console.log(results); // => ["Response body of /resource/A", "Response body of /resource/B"]
});

上記のコードのポイント

  • fetchAB()は逐次的な処理を行った結果を配列のresultsにpushしていき、最終的にそれを返します。
  • thenメソッドでPromiseチェーンしています。
  • 最初のdummyFetch()ではfulfilled状態のPromiseインスタンスを返して配列にvalueを追加した上でさらにdummyFetch()を実行しています。
  • 最初のthenメソッドの中でreturn dummyFetch("/resoutce/B");を行うことでfetchAB()として一つの関数にまとめることができています。
  • response.bodyはプロパティをドット記法で記述する事でvalueを取得しています。

await式を使った場合

function dummyFetch(path) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            if (path.startsWith("/resource")) {
                resolve({ body: `Response body of ${path}` });
            } else {
                reject(new Error("NOT FOUND"));
            }
        }, 1000 * Math.random());
    });
}
// リソースAとリソースBを順番に取得する
async function fetchAB() {
    const results = [];
    const responseA = await dummyFetch("/resource/A");
    results.push(responseA.body);
    const responseB = await dummyFetch("/resource/B");
    results.push(responseB.body);
    return results;
}
// リソースを取得して出力する
fetchAB().then((results) => {
    console.log(results); // => ["Response body of /resource/A", "Response body of /resource/B"]
});
  • 先程との相違点として、fetchABをAsync Functionとawait式で書かれています。
  • fetchAB()内の処理は全て非同期的に処理が行われます。
  • dummyFectch()はPromiseインスタンスを返します。
  • await式の右辺のdummyFetch()の評価結果(Fulfilled,Rejected)を得るまで次の行は実行されません。
  • await式はasyncを宣言していないとsyntax errorになります。
  • await式はAsync Functionの関数の直下とECMAScriptモジュールの直下でしか使用できません。

await式をfunction宣言の中で使用したときのエラー内容

SyntaxError: await is only valid in async functions and the top level bodies of modules

Async Functionとawait式で書くことで同期的な流れと同じように処理を行うことが出来るため、ネストがなく綺麗なコードになります。

Async Functionと反復処理

他の構文との組み合わせとして、for文の例が紹介されていました。 繰り返し処理の中でawait式を使ってリソースの取得を待っています。

function dummyFetch(path) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            if (path.startsWith("/resource")) {
                resolve({ body: `Response body of ${path}` });
            } else {
                reject(new Error("NOT FOUND"));
            }
        }, 1000 * Math.random());
    });
}
// 複数のリソースを順番に取得する
async function fetchResources(resources) {
    const results = [];
    for (let i = 0; i < resources.length; i++) {
        const resource = resources[i];
        // ループ内で非同期処理の完了を待っている
        const response = await dummyFetch(resource);
        results.push(response.body);
    }
    // 反復処理がすべて終わったら結果を返す(返り値となるPromiseを`results`でresolveする)
    return results;
}
// 取得したいリソースのパス配列
const resources = [
    "/resource/A",
    "/resource/B"
];
// リソースを取得して出力する
fetchResources(resources).then((results) => {
    console.log(results); // => ["Response body of /resource/A", "Response body of /resource/B"]
});
  • fetchResourcesに渡したい配列を定義した上で実行しています。
  • ループ内でawaitを使うとdummyFetch()の非同期処理結果を待つことが出来ます。
  • ループ処理が完了したら文字列の要素が入ったresultsの配列が返されます。

    [復習]for文

for文は繰り返す範囲を指定した反復処理を書くことができます。

for (初期化式; 条件式; 増分式) {
    実行する文;
}

for文の実行の流れは次のようになります。

  • 初期化式 で変数の宣言
  • 条件式 の評価結果がtrueなら次のステップへ、falseなら終了
  • 実行する文 を実行
  • 増分式 で変数を更新
  • ステップ2へ戻る

参考

Promiseチェーンをawait式で表現する