見出し画像

NodejsでPromiseなジョブのハンドリング方法について

こんにちわ。nap5です。

今日はNodejsでPromiseなジョブのハンドリング方法について紹介したいと思います。

使用するライブラリはこちらになります。


参考にした実装はこちらになります。


3件のジョブを今回実行したいと思います。

ジョブが成功したら1秒待って次のジョブを実行し、失敗したら3秒待ってロールバック処理を行い、次のジョブを実行します。

今回三種類のデータセットを用意しました。

  • すべて成功するジョブ

  • すべて失敗するジョブ

  • いくつか失敗するジョブ

それぞれ以下のように定義します。

// すべて成功するジョブ
  const jobItemListWithAllOk = [
    new Promise((resolve, reject) => {
      try {
        resolve({ status: 0, result: 1 });
      } catch (error) {
        reject(error);
      }
    }),
    new Promise((resolve, reject) => {
      try {
        resolve({ status: 0, result: 2 });
      } catch (error) {
        reject(error);
      }
    }),
    new Promise((resolve, reject) => {
      try {
        resolve({ status: 0, result: 3 });
      } catch (error) {
        reject(error);
      }
    }),
  ];
// すべて失敗するジョブ
  const jobItemListWithAllNg = [
    new Promise((resolve, reject) => {
      try {
        resolve({ status: 1, result: new Error("!!!") });
      } catch (error) {
        reject(error);
      }
    }),
    new Promise((resolve, reject) => {
      try {
        resolve({ status: 1, result: new Error("!!!") });
      } catch (error) {
        reject(error);
      }
    }),
    new Promise((resolve, reject) => {
      try {
        resolve({ status: 1, result: new Error("!!!") });
      } catch (error) {
        reject(error);
      }
    }),
  ];
// いくつか失敗するジョブ
  const jobItemListWithSomethingError = [
    new Promise((resolve, reject) => {
      try {
        resolve({ status: 1, result: new Error("!!!") });
      } catch (error) {
        reject(error);
      }
    }),
    new Promise((resolve, reject) => {
      try {
        resolve({ status: 0, result: 2 });
      } catch (error) {
        reject(error);
      }
    }),
    new Promise((resolve, reject) => {
      try {
        resolve({ status: 1, result: new Error("!!!") });
      } catch (error) {
        reject(error);
      }
    }),
  ];


プログラムは以下になります。


// https://stackoverflow.com/a/36757699
import Q from "q";

function workCollection({ promiseList, statusCountInfo }) {
  return promiseList.reduce(function (promise, item, index) {
    return promise
      .then(function ({ previousStatus = true, statusCountInfo }) {
        console.log("previousStatus", previousStatus);
        if (previousStatus) {
          return Q.delay(1000);
        }
        return Q.delay(3000);
      })
      .then(async function (result) {
        // main process
        const { status, result: _result } = { ...(await item) };
        if (status === 1) {
          statusCountInfo.fail = statusCountInfo.fail + 1;
          return (() => {
            console.log("Unhappy Cowboy Bebop", _result); // teardown process then rollback process
            return { previousStatus: false, statusCountInfo }; // next status;
          })();
        }
        return (() => {
          statusCountInfo.success = statusCountInfo.success + 1;
          console.log("Happy Cowboy Bebop", _result); // teardown process and next prepare process
          return { previousStatus: true, statusCountInfo }; // next status;
        })();
      });
  }, Q(true));
}

function doWorkCollection({ promiseList, statusCountInfo }) {
  Q()
    .then(function () {
      console.log("start");
      Object.assign(statusCountInfo, { success: 0, fail: 0 });
      return [promiseList, statusCountInfo];
    })
    .then(([promiseList, statusCountInfo]) => {
      return workCollection({ promiseList, statusCountInfo });
    })
    .then(function ({ previousStatus, statusCountInfo }) {
      console.log("done");
      console.log(statusCountInfo);
    });
}

(() => {
// すべて成功するジョブを実行
  doWorkCollection({ promiseList: jobItemListWithAllOk, statusCountInfo: {} });
// すべて失敗するジョブを実行
  doWorkCollection({ promiseList: jobItemListWithAllNg, statusCountInfo: {} });
// いくつか失敗するジョブを実行
  doWorkCollection({
    promiseList: jobItemListWithSomethingError,
    statusCountInfo: {},
  });
})();


結構難易度は高めなんですが、データベースの操作が関係してくる場合などでは、結構便利なコードができたので、使いまわせるのではと思います。

では、それぞれ実行していきましょう。

すべて成功するジョブの場合です。

3秒ぐらいで終了していますね。

$ time node -r esm index.js
start
previousStatus true
Happy Cowboy Bebop 1
previousStatus true
Happy Cowboy Bebop 2
previousStatus true
Happy Cowboy Bebop 3
done
{ success: 3, fail: 0 }

real    0m3.173s
user    0m0.166s
sys     0m0.009s


すべて失敗するジョブの場合です。

7秒ぐらいで終了していますね。(初回実行は待ち時間を除いて、2回目以降待ち)

$ time node -r esm index.js
start
previousStatus true
Unhappy Cowboy Bebop Error: !!!
    at require.resolve.status (/home/gri-user/wrksp/cool/index.js:76:38)
    at new Promise (<anonymous>)
    at /home/gri-user/wrksp/cool/index.js:74:5
    at Object.<anonymous> (/home/gri-user/wrksp/cool/index.js:125:3)
    at Generator.next (<anonymous>)
previousStatus false
Unhappy Cowboy Bebop Error: !!!
    at require.resolve.status (/home/gri-user/wrksp/cool/index.js:83:38)
    at new Promise (<anonymous>)
    at /home/gri-user/wrksp/cool/index.js:81:5
    at Object.<anonymous> (/home/gri-user/wrksp/cool/index.js:125:3)
    at Generator.next (<anonymous>)
previousStatus false
Unhappy Cowboy Bebop Error: !!!
    at require.jobItemListWithAllNg (/home/gri-user/wrksp/cool/index.js:90:38)
    at new Promise (<anonymous>)
    at /home/gri-user/wrksp/cool/index.js:88:5
    at Object.<anonymous> (/home/gri-user/wrksp/cool/index.js:125:3)
    at Generator.next (<anonymous>)
done
{ success: 0, fail: 3 }

real    0m7.173s
user    0m0.131s
sys     0m0.040s


いくつか失敗するジョブ
5秒ぐらいで終了していますね(初回実行は待ち時間を除いて、2回目以降待ち)

$ time node -r esm index.js
start
previousStatus true
Unhappy Cowboy Bebop Error: !!!
    at require.resolve.status (/home/gri-user/wrksp/cool/index.js:99:38)
    at new Promise (<anonymous>)
    at /home/gri-user/wrksp/cool/index.js:97:5
    at Object.<anonymous> (/home/gri-user/wrksp/cool/index.js:125:3)
    at Generator.next (<anonymous>)
previousStatus false
Happy Cowboy Bebop 2
previousStatus true
Unhappy Cowboy Bebop Error: !!!
    at require.jobItemListWithSomethingError (/home/gri-user/wrksp/cool/index.js:113:38)
    at new Promise (<anonymous>)
    at /home/gri-user/wrksp/cool/index.js:111:5
    at Object.<anonymous> (/home/gri-user/wrksp/cool/index.js:125:3)
    at Generator.next (<anonymous>)
done
{ success: 1, fail: 2 }

real    0m5.189s
user    0m0.160s
sys     0m0.032s


使うときは引数にプロミスなジョブ配列を渡して、処理情報のオブジェクト渡してやれば、いいかんじですかね。

適宜、teardown関数やrollup関数、nextSetUp関数なども引数に渡して汎用化できるようにしておくといいかもしれません。

doWorkCollection({ promiseList: jobItemListWithAllNg, statusCountInfo: {} });


ちなみにCowBoy Bebopはアニメの名前です。

簡単ですが、以上です。



この記事が気に入ったらサポートをしてみませんか?