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はアニメの名前です。
簡単ですが、以上です。
この記事が気に入ったらサポートをしてみませんか?