自己紹介
初めまして。kyohei-harada と申します。
弊社の SRE兼クラウド推進部 に所属しており、インフラ、サーバ、OS、ミドルウェア等、色々と関わらせて頂いております。
技術ブログへの投稿を通して、ひとまずはアウトプットの習慣化を実現しつつ、自身の苦手なプログラミング・アプリ周りの領域にも挑戦し、いずれはフルスタックなエンジニアを目指してゆきたいと考えています。
どうぞよろしくお願いします。
この記事について
私は最近 JavaScript(Node.js) にハマりつつあるのですが、Promise での順次・並列実行時のコードがイメージできなかったので、自身のルーチンをモデル化することで理解を深めることにしました。
UML や JavaScript(Node.js) などに興味がある方や、身近なものをモデル化したい方には、参考にしてもらえると嬉しいです。
朝のルーチンのモデル化
私はたまに、典型的な朝ごはんというのを無性に食べたくなるタイミングがあります。
その際の動きをモデル化してみました。
UML アクティビティ図 ※PlantUML コード
@startuml
start
fork
:トーストを焼く;
:ケトルでお湯を沸かす;
fork again
fork
:目玉焼きを焼く;
fork again
:ウインナーを焼く;
end fork
end fork
:コーヒーをドリップする;
:盛り付ける;
:完成!;
stop
@endumlよいですね、ごきげんな朝ごはんです。
実際の図は下記になります。
オーブントースターやケトル等を利用する処理は、直列処理になるかと思います。
私の自宅の電源コンセント的には、両方同時に動かすとブレーカーが落ちる、という意味でも。
フライパンを使った調理は、もちろん並列処理ですね。
別々で調理するなんて丁寧な真似はできません。朝の時間はそれなりに貴重なので。
あらかた料理が仕上がった後に、コーヒーをドリップしてから料理を盛り付けて、完成。
こんな感じではないでしょうか。
コード化
上記をコード化してみました。
const processDetail = {
toast: {
summary: "トーストを焼く",
messageStart: "トーストをオーブントースターで焼きます",
messageRunning: "トーストを焼いています",
messageEnd: "トーストが焼けました",
cookingTime: 5, // 秒
},
hotWater: {
summary: "お湯を沸かす",
messageStart: "ケトルでお湯を沸かします",
messageRunning: "お湯を沸かしています",
messageEnd: "お湯が沸きました",
cookingTime: 3 // 秒
},
friedEgg: {
summary: "目玉焼きを焼く",
messageStart: "目玉焼きを焼きます",
messageRunning: "目玉焼きを焼いています",
messageEnd: "目玉焼きが焼けました",
cookingTime: 8 // 秒
},
wienerSausage: {
summary: "ウインナーを焼く",
messageStart: "ウインナーを焼きます",
messageRunning: "ウインナーを焼いています",
messageEnd: "ウインナーが焼けました",
cookingTime: 6 // 秒
},
coffee: {
summary: "コーヒーを淹れる",
messageStart: "コーヒーを淹れます",
messageRunning: "コーヒーをドリップしています",
messageEnd: "コーヒーがドリップできました"
},
arrange: {
summary: "盛り付け",
messageStart: "盛り付けします",
messageRunning: "盛り付けています",
messageEnd: "できあがり!"
},
};
class CookProcess {
constructor(processDetail) {
this.summary = processDetail.summary;
this.messageStart = processDetail.messageStart;
this.messageRunning = processDetail.messageRunning;
this.messageEnd = processDetail.messageEnd;
typeof processDetail.cookingTime === "undefined"
? (this.cookingTime = Math.random() * 5 * 1000)
: (this.cookingTime = processDetail.cookingTime * 1000);
}
cook() {
console.log(new Date().toISOString() + ":" + this.messageStart);
console.log(new Date().toISOString() + ":" + this.messageRunning);
return new Promise((resolve)=>{
setTimeout(()=>{
resolve(new Date().toISOString() + ":" + this.messageEnd);
},
this.cookingTime)
})
}
}
async function main(){
toast = new CookProcess(processDetail.toast);
hotWater = new CookProcess(processDetail.hotWater);
friedEgg = new CookProcess(processDetail.friedEgg);
wienerSausage = new CookProcess(processDetail.wienerSausage);
coffee = new CookProcess(processDetail.coffee);
arrange = new CookProcess(processDetail.arrange);
p1 = toast.cook()
.then((v)=>{
console.log(v);
return hotWater.cook();
})
.then((v)=>console.log(v));
p2 = Promise.all([friedEgg.cook(),wienerSausage.cook()]).then((values)=>{
values.map((v)=>console.log(v))
})
Promise.all([p1, p2]).then(()=>{
return coffee.cook();
}).then((v)=>console.log(v))
.then(()=>arrange.cook())
.then((v)=>console.log(v))
}
main();実行結果
2023-03-23T13:56:51.273Z:トーストをオーブントースターで焼きます
2023-03-23T13:56:51.283Z:トーストを焼いています
2023-03-23T13:56:51.286Z:目玉焼きを焼きます
2023-03-23T13:56:51.286Z:目玉焼きを焼いています
2023-03-23T13:56:51.287Z:ウインナーを焼きます
2023-03-23T13:56:51.287Z:ウインナーを焼いています
2023-03-23T13:56:56.290Z:トーストが焼けました
2023-03-23T13:56:56.290Z:ケトルでお湯を沸かします
2023-03-23T13:56:56.291Z:お湯を沸かしています
2023-03-23T13:56:59.289Z:目玉焼きが焼けました
2023-03-23T13:56:57.290Z:ウインナーが焼けました
2023-03-23T13:56:59.291Z:お湯が沸きました
2023-03-23T13:56:59.292Z:コーヒーを淹れます
2023-03-23T13:56:59.292Z:コーヒーをドリップしています
2023-03-23T13:57:03.490Z:コーヒーがドリップできました
2023-03-23T13:57:03.491Z:盛り付けします
2023-03-23T13:57:03.492Z:盛り付けています
2023-03-23T13:57:05.191Z:できあがり!まとめ
promise オブジェクトの変数への代入や、Promise.all, then などを組み合わせることで、イメージ通りの順序で処理を動かすことができました。
内容としてはまだまだ拙いのでこれから精進してゆきたい所存ですが、身近なものをモデル化する、という点は楽しく感じました。
今後も身近なものをどのようにモデル化できるかを意識しつつ、遊び心を忘れずに取り組んでゆきたいです。
