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

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

2022年2月17日 非同期処理:コールバック/Promise/Async - Promiseチェーンで値を返す (JavaScript Primer)

Promiseチェーンで値を返す

thenメソッドなどのコールバック関数は次のチェーンに値を渡すこともできます。 以下はreturnして次のthenメソッドの引数に値を渡している例です。

Promise.resolve(1).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
});

3つ目のthenで値をreturnしていないので4つ目のthenの引数であるvalueはundefinedとなります。

コールバック関数でPromiseインスタンスを返す

Promise.rejectでPromiseState内部プロパティをrejectedの状態でインスタンス化したものをreturnしているので、thenは無視されてcatchに処理が移ります。 これまではfulfilledを返すと学びましたが、このような手法でrejected を返すことで、その後のPromiseチェーンでthenではなくcatchの処理をさせる事ができます。

Promise.resolve().then(function onFulfilledA() {
    return Promise.reject(new Error("失敗"));
}).then(function onFulfilledB() {
    console.log("onFulfilledBは呼び出されません");
}).catch(function onRejected(error) {
    console.log(error.message); // => "失敗"
}).then(function onFulfilledC() {
    console.log("onFulfilledCは呼び出されます");
});

以下のように、catchの処理の中でrejectをreturnさせて、エラー処理を次のチェーンへと連続させることも出来ます。

function main() {
    return Promise.reject(new Error("エラー"));
}
// mainはRejectedなPromiseを返す
main().catch(error => {
    // mainで発生したエラーのログを出力する
    console.log(error);
    // Promiseチェーンはそのままエラーを継続させる
    return Promise.reject(error);
}).then(() => {
    // 前のcatchでRejectedなPromiseが返されたため、この行は実行されません
}).catch(error => {
    console.log("メインの処理が失敗した");
});

[ES2018]Promiseチェーンの最後に処理を書く

Promiseのfinallyメソッドは成功時、失敗時どちらの場合でも呼び出されるコールバック関数を登録できます。 try...catch...finally構文のfinally節と同様の役割を持つメソッドです。

以下はresolve()とreject()のどちらかをランダムに発生させ、thenかcatchを行った後にfinallyの処理を呼び出しています。

const promise = Math.random() < 0.5 ? Promise.resolve() : Promise.reject();
promise.then(() => {
    console.log("Promise#then");
}).catch((error) => {
    console.log("Promise#catch");
}).finally(() => {
    // 成功、失敗どちらの場合でも呼び出される
    console.log("Promise#finally");
});

次のコードでは、リソースを取得してthenで成功時の処理、catchで失敗時の処理を登録しています。 また、リソースを取得中かどうかを判定するためのフラグをisLoadingという変数で管理しています。 成功失敗どちらにもかかわらず、取得が終わったらisLoadingはfalseにします。 thenとcatchの両方でisLoadingへfalseを代入できますが、finallyメソッドを使うことで代入を一箇所にまとめられます。

let isLoading = true; のフラグを処理が終わったらfalseにしたいわけですが、finallyメソッドを使わないとthenとcatchそれぞれに書かなくてはいけないところを、必ず最後に実行されるfanallyメソッドを使う事で一箇所にまとめられています。

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());
    });
}
// リソースを取得中かどうかのフラグ
let isLoading = true;
dummyFetch("/resource/A").then(response => {
    console.log(response);
}).catch(error => {
    console.error(error);
}).finally(() => {
    isLoading = false;
    console.log("Promise#finally");
});

まとめ

  • Promiseチェーンではコールバックで返した値を次のコールバックの引数として渡せます
  • catch内でPromise.rejectメソッド使ってPromiseStaterejectedで返せばその後のPromiseチェーンのthenを無視させcatchの処理を行えます
  • finallyメソッドはPromiseState内部プロパティがfulfilled rejectedどちらでも処理を実行します

参考

Promiseチェーン