JavaScript
promise

複数Promise実装時のベストプラクティス

複数のPromiseを組み合わせるのって難しいですよね?
毎回30分くらいは悩みます。ん?30分?君達やっぱりプロミスだな!
ということでPromiseと格闘して見えてきたTipsをまとめておきます。

複数Promiseを組む際の参考プログラム

複数のPromiseをつなげるときはgoogleの中の人のサンプルを参考にしています。

asyncThing1().then(function() {
    return asyncThing2();
}).then(function() {
    return asyncThing3();
}).catch(function(err) {
    return asyncRecovery1();
}).then(function() {
    return asyncThing4();
}, function(err) {
    return asyncRecovery2();
}).catch(function(err) {
    console.log("Don't worry about it");
}).then(function() {
    console.log("All done!");
})

ダウンロード.png

複雑。。。フローチャートがあるから理解できてる状態です。

検証用スクリプトで試す

実装時、意図した通りに動くかテスト用スクリプトを書きました。
成功、失敗をtrue/falseで定義して試せますのでよければ。
上記サンプルだとこんな感じ。

(function() {
    'use strict';

    const is_success = {
        'asyncThing1' : true,
        'asyncThing2' : true,
        'asyncThing3' : true,
        'asyncThing4' : true,
        'asyncRecovery1' : true,
        'asyncRecovery2' : true,
    }

    function promiseTest(name) {
        console.log(name + ' start');
        return new Promise(function(resolve, reject) {
            if (is_success[name]) {
                resolve(name + ' succeed');
            } else {
                reject(new Error(name + ' fail'));
            }
        });
    }

    promiseTest('asyncThing1').then(function() {
      return promiseTest('asyncThing2');
    }).then(function() {
      return promiseTest('asyncThing3');
    }).catch(function(err) {
      return promiseTest('asyncRecovery1');
    }).then(function() {
      return promiseTest('asyncThing4');
    }, function(err) {
      return promiseTest('asyncRecovery2');
    }).catch(function(err) {
      console.log("Don't worry about it");
    }).then(function() {
      console.log("All done!");
    })

})();

複雑になりそうなら別関数化する

例えば上記サンプルに

  • asyncThing4がコケたらasyncRecovery3を実行
  • asyncRecovery3がコケたらasyncRecovery4を実行

といった処理を追加をすると、どんどんネストが深くなって複雑になりそうです。

{省略}
}).then(function() {
    return asyncThing4().then(function() {
    }, function(err) {
        return asyncRecovery3().then(function() {
        }, function(err) {
            return asyncRecovery4();
        });
    });
}, function(err) {
    return asyncRecovery2();
}).catch(function(err) {
    console.log("Don't worry about it");
{省略}

こんな感じでしょうか。(リカバリ成功時に"Don't worry about it"が出力されないので正しくはないですが...)
メンテナンスも大変なのでエラー処理も含めて関数化するとスッキリしそうです。

function asyncThing4Wrap() {
    return asyncThing4().catch(function() {
        return asyncRecovery3();
    }).catch(function() {
        return asyncRecovery4();
    });
}

PromiseをPromiseでwrapする

エラー発生時、エラー内容をログ出力したりするかと思います。

taskA().then(function(res) {
    console.log(res) // taskA成功時の情報
    return taskB(); // taskA成功時に行うべき処理
}).catch(function(err) {
    console.log(err) // taskA(またはB)のエラー内容
    return taskC(); // taskA失敗時に行うべき処理
})

メソッドチェーンに記載すると可読性が悪くなるのでこんな時も別関数化するといいかと。
ライブラリ使用時なんかもWrapしておくと問題の切り分けがしやすいです。
メイン処理のメソッドチェーンでは本来行うべきことだけ記載しましょう。

taskAWrap().then(function(res) {
    return taskBWrap(); // A成功時に行うべき処理
}).catch(function(err) {
    return taskCWrap(); // A失敗時に行うべき処理
})

function taskAWrap() {
    return taskA().then(function(res) {
        console.log(res) // A成功時の情報
        return Promise.resolve(res); // ※return res;でも構わない。catch側はPromise.rejectで書く必要があるため統一してる。
    }).catch(function(err) {
        console.log(err) // Aのエラー内容
        return Promise.reject(err);
    });
}

第2引数のエラー処理は極力使わない

promise.then(function(res) {
    console.log('成功');
}, function(err) {
    console.log('失敗');
});

第二引数でエラー処理を記述できますが、可読性が悪くなるのでよほどのことがない限りthenとcatchで統一がいいかなと。

promise.then(function(res) {
    console.log('成功');
}).catch(function(err) {
    console.log('失敗');
});

thenとcatchをif/elseのように使いたくなるけどダメ絶対!

最初悩みすぎてやりそうになったのがコレ。
ある処理で結果がAならresolve、Bならrejectで返して、then/catchで分岐しようと実装していました。
Promiseはあくまで処理なので成功か失敗しかない。(この場合A,Bともに処理は成功しているのでresolve)

複数のPromiseからデータ取得したあとに何かしら処理する

Promise.allを使うと並列で処理を行い、全て完了したタイミングでthenが実行されます。

var promises = [];
promises.push(taskA());
promises.push(taskB());
return kintone.Promise.all(promises).then(function(response) {
    // 呼び出した順にレスポンスが格納されている
    var responseA = response[0];
    var responseB = response[1];
    return taskC(responseA, responseB);
}).catch(function(err) {
    // 最初に失敗したPromiseのエラー内容。その他のPromiseは処理を継続する
    console.log(err);
});