モチベーション
「複数の仕事をする際に、反応を待たないで次の処理を実行する」という概念を現実的なシナリオに当てはめてみます。あなたのライフスタイルを通じて、非同期処理の考え方を理解しましょう。
非同期のライフスタイル
あなたは趣味でトマトをプランターで栽培しています。最初に、トマトの種をプランターの土に埋めました。トマトの成熟するまでの時間は分からないものの、実ったら美味しいカプレーゼを楽しもうと妄想します。その後、あなたはいつも通りの生活を過ごします。
あなたはプログラマーで、いつも家の窓際でPC作業をしています。時々窓の外に目を移して、プランターの様子を伺います。
2か月後、ついにその日がやってきました。赤いトマトが実ったのです!計画通り、トマトでカプレーゼを作ることにしました。トマトをスライスし、モッツァレラチーズと交互に並べ、オリーブオイルを完成!そして、辛口の白ワインと舌鼓、ああ、至福の時間…。
解説
この場合、あなたの振る舞いは非同期的です!あなたはトマト栽培とプログラミングのタスクを抱えていますが、トマトの成熟する間に他の作業もこなしています。もしいつ成熟するか分からないのにプランターの前で座していれば、プログラミングの仕事も進まないでしょう。実に賢明です。
そして、トマトが実ったら、待ってましたとばかりにカプレーゼに仕立て上げた手際も見事です!前々から調理するところを妄想していましたからね。
このように、トマトの成熟を待つ間に他の作業をこなし、成果を最大にする姿勢は非同期処理のイメージそのものです。
Promiseの例
このシナリオをTypeScriptで表現すると以下のようになります。
function live() {
const tomatoEnjoy = new Promise<string>((resolve) => {
// トマトが成熟する、これは時間がかかる処理
// resolve(トマト);
});
tomatoEnjoy.then((トマト) => {
// トマトをスライスする
// トマトとチーズを交互に並べる
// オリーブオイルをかける
// 白ワインと一緒に楽しむ
}).catch((error) => {
// トマトが枯れてしまった…
});
// ここで、プログラミングの仕事を繰り返す
}
live();
Promiseを使った場合、then()の中でPromise完遂した時(トマトが実った後)にどうするかを記述することになります。また、catch()の中でPromise失敗した時(トマトが枯れた後)にどうするかを記述できます。
同期のライフスタイル(?)
もしくは、あなたはプランターの前でトマトが実るのを待つかもしれません。実る時期も、実るかどうかすら分かりません。それでもカプレーゼが楽しみで仕方がないのです!
Promiseの例
プログラマーの仕事(それだけでなく、食事や睡眠も!)をカプレーゼの後に行うことを選ぶなら、トマトの成熟をawaitしましょう。
async function live() {
const tomatoEnjoy = new Promise<string>((resolve) => {
// トマトができる、時間がかかる処理
// resolve(トマト);
});
await tomatoEnjoy.then((トマト) => {
// スライスする
// チーズと交互に並べる
// オリーブオイルをかける
// 白ワインと一緒に楽しむ
}).catch((error) => {
// トマトが枯れてしまった…
});
// ここで、プログラミングの仕事を処理を繰り返す
}
live();
ここで、live()メソッドにも async キーワードが付加されていることに注目してください。awaitを使用する際には、それを含むメソッドをasyncに変更する必要があります。つまり、live()メソッド全体を非同期にします。
解説
こんなふうに考えるかもしれません。「トマト栽培とプログラミングを順番=同期的に行ったのに、どうして非同期なんだ!?」と。
じっくりと考えてみましょう。あなたがプログラミングの仕事に取りかかるタイミングは、いつトマトが実るかに影響されています。あなたの上司は、あなたがいつ与えたタスクを完了してくれるのか分かりません。あなたの奥さんは、あなたがいつ植木鉢とのにらめっこをやめて、家事を手伝ってくれるのか分かりません。
確かにあなたは自分の行動を同期的に進めています。しかし、あなたの周囲の人々にとって、あなたの生活は非同期的すぎるのです!おそらく、トマトが終わるのを見計らって、上司から次のタスクを、奥さんからお皿洗いの手伝いを頼まれるでしょう。
トマトを栽培しつつ、同期的に生活する(かつ、人間関係を損なわない)なんて無理な話なのです。トマトが成熟するタイミングが分からないのですから…
おまけ:動くコード
ぜひ、TypeScript Playgroundで試してみてください。
function live() {
const tomatoEnjoy = new Promise<string>((resolve) => {
let crop = 'tomato';
let harvestPeriod = 5000;
// 収穫に失敗した場合
// throw new Error();
// 収穫に成功した場合
setTimeout(() => {
resolve(crop);
}, harvestPeriod);
});
// ここにawaitを加えることで、トマトの成長を待つことができる
// その場合functionの先頭にasyncを加える
tomatoEnjoy.then((crop: string) => {
console.log('Cooking!');
console.log('Enjoy ' + crop + '!');
}).catch((error) => {
console.log('Failure...');
console.log(error);
});
console.log('programming life...');
}
live();
TypeScript初学者なので、解釈が間違っていることもあるかもしれません。何かお気づきの点がございましたら、ご指摘いただけると幸いです。