TypeScript上で非同期処理を挟んでみるところに遭遇したがうまく型付けできず改めて基礎を振り返ると同時に記事にまとめてみた。
なお、async / await は TypeScript1.7以降に対応している
(ただし 1.7v は es6。 2.1v から es5/es3 に対応)。
前置き(1/2) 前提として Promise の理解が必要
もう知っている方は飛ばしてもられば。Promiseの動きが分かっていないとTypeScriptの async / await は理解しづらい。
ご存知かもしれないが、Promiseに対する async / await は Promise を読み書きしやすいよう別の構文に置き換えたもの、つまりシンタックスシュガーである。
画像のようにVSCodeにおけるasync関数の型の推測では返り値に Promise が読み込まれていることがわかる。
前置き(2/2) Promiseってなんだっけ(復習)
const repairPromise = new Promise((resolve, reject) => {
const repair = true;
if(!repair) reject('修理してください');
return setTimeout(() => resolve('修理しました'), 3000);
});
const paymentPromise = new Promise((resolve, reject) => {
const payment = true;
if(!payment) reject('送金まだですか?');
return setTimeout(() => resolve('送金しました'), 500);
});
console.log('進捗どうですか?')
repairPromise
.then(res => {
console.log(res);
return paymentPromise.then(res => console.log(res)).catch(err => console.log(err));
})
.catch(err => console.log('errorめっせー', err));
電気屋さんの修理と支払いのPromiseチェーンを書いた。
「電気屋さんに修理を約束した(Promise)」→「修理できた(resolve)」→「次の処理(支払いPromiseへ)」
もしくは
「電気屋さんに修理を約束した(Promise)」→「修理できなかった(reject)」→「エラー処理(catch)」
上例のようにPromiseを用いることで電気屋さんの修理という処理が実行した後でないと resolve も reject という第二の処理が実行できない。必ず順番に行う必要がある。
setTimeout(() => resolve('修理しました'), 3000);
setTimeout(() => resolve('送金しました'), 500); //Promiseでないなら先に処理を終えている
もしも Promise で非同期処理を宣言していなければ、setTimoutが500であるpaymentPromiseが先に実行されてしまう。
JavaScript(nodeJS)は非同期処理で動く。非同期の処理が階層として深くなってしまう(ネストし過ぎる)と電気屋さんに修理を果たしてくれた後に実行したいこと(例えばお金を支払う)を先に処理してしまうような思いがけない順番の挙動となることがある。
そうならないため処理を Promise として宣言することが必要。宣言した処理が完遂すると次の処理を then で実行する。
通常の async / await の場合
先ほどの処理を async / await に置き換えてみた。 await として呼び出す関数はPromiseである。
const asyncFunc = async() => {
console.log('進捗どうですか');
try{
const msg1 = await repairAsync(3000);
console.log(msg1);
const msg2 = await paymentAsync(500);
console.log(msg2);
} catch(e) {
console.log(e);
}
};
const repairAsync = (ms) => new Promise(resolve => {
setTimeout(() => {
resolve('修理しました')
},ms);
});
const paymentAsync = (ms) => new Promise(resolve => {
setTimeout(() => {
resolve('送金しました')
},ms);
});
asyncFunc();
蛇足として、先ほどと同じ処理だがawaitをただ列挙するだけだと、最初のPromiseオンリーとは異なり同期的な処理となる。つまり、1つ目のawaitが終わるまで2つ目のawaitで処理を進めておくことができない。
setTimeoutの処理を1つずつにしか処理できない。
async / await を最小構成で表すと
画像のように Promise処理 に対して await をつけてあげることで同期的に処理することができる。
const asyncTest = async() => {
await new Promise(resolve => setTimeout(resolve, 3000));
console.log('finish');
}
asyncTest();
本題:TypeScriptで async / await の使い方
Promiseの説明で前置きが少し長くなったがここで先ほどのコードを async / await を書いてみた
const asyncFunc = async(): Promise<void> => {
console.log("進捗どうですか");
try {
const msg1 = await repairAsync(3000);
console.log(msg1);
const msg2 = await paymentAsync(500);
console.log(msg2);
} catch (e) {
console.log(e);
}
};
const repairAsync = (ms: number): Promise<string> =>
new Promise((resolve) => {
setTimeout(() => {
resolve("修理しました");
}, ms);
});
const paymentAsync = (ms: number): Promise<string> =>
new Promise((resolve) => {
setTimeout(() => {
resolve("送金しました");
}, ms);
});
asyncFunc();
(codesandbox: https://codesandbox.io/s/boring-shadow-1pulk?fontsize=14&hidenavigation=1&theme=dark )
async / await と Promise の関係がわかれば特に難しいことはない。
resolveとしての返り値の型が Promise と、ジェネリック型となっている。使い方と言うまでもないかもしれない。
Promiseのジェネリック型について
ジェネリクスは型を生成するテンプレを作ることができるTypeScriptの機能。
型を入れる前はPromiseで表されるのだが、はもちろんの略称。
ジェネリック型についてはTypeScript Deep Diveが参考になる。