複数の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 sleep(s) {
        return new Promise(resolve => setTimeout(resolve, s * 1000));
    }

    async function promiseTest(name) {
        console.log(name + ' start');
        await sleep(1);
        return new Promise(function(resolve, reject) {
            if (is_success[name]) {
                console.log(name + ' succeed');
                resolve(name + ' succeed');
            } else {
                console.log(name + ' fail');
                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");
{省略}

こんな感じでしょうか。
メンテナンスも大変なのでエラー処理も含めて関数化するとスッキリしそうです。

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; でもOK
    }).catch(function(err) {
        console.log(err) // Aのエラー内容
        return Promise.reject(err); // throw err; でもOK
    });
}

ポイントはcatch内でreject(またはthrow)すること。これをしないと呼び出し側のcatchに入らずthenが続いてしまいます。呼び出し側にもエラーと知らせるために忘れずthrowしましょう!

第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);
});

await の挙動

await していた場合は then や catch 内の処理も終わるまで待つんだっけ?となったのでメモ。
正解は待つ。
promiseTest('asyncThing1') でawaitした場合、’All done!’ のあとに Next task! が表示される。

(async function() {
    'use strict';

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

    function sleep(s) {
        return new Promise(resolve => setTimeout(resolve, s * 1000));
    }

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

    await 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!");
    })

    console.log("Next task!");

})();