async await と try catch と時々 fetch、あとTS
なにこれ
こいつら何が何だか分からないので分からないことを解消していきつつまとめる
個々についてはある程度理解がある前提のメモ
fetchについて重要なこと
Promise<Response> を返す
Promise を返す。なので fetch の処理は await 演算子を付与でき、Promiseが解決するのを待つ async function として書くことができる。
code:ts
async function getData(url: string, options: object): Promise<string> {
const response = await fetch(url, options);
return response.text();
}
getData('v1/hoge', {}).then(r => console.log(r));
400, 500などのエラーは例外としてスローされない
ネットワークエラーに遭遇すると fetch() promise は TypeError を返して reject 状態になります。サーバー側の CORS が適切に設定されていない場合も同様です(アクセス権の問題ですけどね) — 一方で例えば 404 はネットワークエラーを構成しません。fetch() が成功したかどうかの明確な判定をするには、プロミスが解決されて、Response.ok プロパティが true になっているかなどを確認します。次のようなコードになるでしょう
なので then のなかでレスポンスのステータスコードを見て、ダメならエラーを返すようにするのがよくあるパターン
code:ts
fetch('flowers.jpg')
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.blob();
})
.catch(e => console.log('ここはネットワークエラー、つまりリクエストに失敗した場合'));
await について重要なこと
※ async await がセットであることは省略
await 演算子を付与できる式には3種類ある。
1. Promise ( fullfill されるやつ )
2. Promise ( reject されるやつ )
3. Promiseじゃない素の値 (1と同様に扱ってくれる)
※1, 2は同じだが返り値が違うので区別した
1. fullfill される Promise:解決された値が返るのを待つ
await を使う場合で最も思い浮かべるであろう一般的な挙動
2. reject される Promise:rejectを例外に変換して返す
code:ts
async function f3() {
try {
var z = await Promise.reject(30);
} catch (e) {
console.log(e); // 30
}
}
f3();
重要: await が reject を例外に変換するため、 try-catch文でキャッチすることができる。
また、try-catch文を使わずとも promise.catch() でエラーハンドリングできる
code:ts
var response = await promisedFunction().catch((err) => { console.log(err); });
// promiseがrejectされた場合、response には undefined が代入されます
もちろん await は async の中でしか使えないので、Async Function 内で Promise を返す関数に await 演算子を付与して呼び出せば、その関数が返す reject をエラーとしてハンドリングできる という認識でOK。
※ try-catch文の中に async await を書いてもtry-catchでハンドリングできない。↑のリンクを参照。
async について重要なこと
何をしようが絶対Promiseを返す (Implicit Promise)
await 演算子を付与する式の返り値が Promise でもそれ以外でも扱えるように、
async 演算子を付与した関数の返り値は Promise になる。
※ 既にPromiseを返す演算子に更にPromiseを加えて Promise<Promise<string>> のようにはならない。
awaitのエラーをcatchしない場合
前述の通り、 await のエラーは async 関数内部の try-catch文 or Promise.catch() でハンドリングできる。
しかし関数内部でハンドリングしない場合はどうなるのか?
code:ts
async function concurrentStart() {
console.log('==CONCURRENT START with await==');
// 1. ここは即時実行される
const slow = resolveAfter2Seconds() // 即時実行
const fast = resolveAfter1Second() // 即時実行
console.log(await slow) // 2. ここは 1. の2秒後に実行される
console.log(await fast) // 3. ここは 1. の2秒後(2.の直後)に実行される
}
もし slow or fast 内で関数呼び出しが失敗したら…?
(await 演算子のおかげで)rejectionは例外として変換され
(async 演算子のおかげで)例外が自動的に補足されて実行中の非同期関数が中断されて (あやふや)
(async 演算子のおかげで)例外を Promise (reject) に包んで返す (Implicit Promise)
ので、concurrentStart の外側では Promise.catch() としてハンドリングできる。
※ Promise の reject は try-catchでハンドリングできない。
code:ts
(() => {
try {
Promise.reject();
} catch (e) {
console.log('こんなことをしてもキャッチできない')
}
})();
TSだと返り値は Promise<Promise<string>> みたいな型になる時ないんか
そんなことには原理的にならんはず。
非同期処理をネストせずに済むようにPromiseがあるのだし、以下のようにネストしても返り値は Promise<string> で、resolveされるべき値が入る
code:ts
function higherPromise(): Promise<string> {
return new Promise((resolve) => {
setTimeout(() => {
console.log('outer promise');
resolve(
new Promise((r) => {
setTimeout(() => {
console.log('inner promise');
r('resolved');
}, 2000);
})
);
}, 2000);
});
}