A: 逐次処理と中間結果利用を、関心に基づいて分離できる
Async Generator (Async Iterator with Generator) とは
async function*
で定義できるGeneratorのこと。
例:
// Delay utility const asyncProcessing = (output: number): Promise<number> => new Promise((resolve) => setTimeout(() => resolve(output), 5 * 1000)); // Generator definition async function* AsyncGenerator() { yield { type: "start", payload: "start processing" }; const step1 = await asyncProcessing(1); yield { type: "processing", payload: "step1 finished" }; await asyncProcessing(step1); yield { type: "processing", payload: "step2 finished" }; yield { type: "end", payload: "processed" }; } // Usecase (async () => { for await (const action of AsyncGenerator()) { console.log(JSON.stringify(action)); } })(); // { type: "start", payload: "start processing" } // (wait 5 sec) // { type: "processing", payload: "step1 finished" } // (wait 5 sec) // { type: "processing", payload: "step2 finished" } // { type: "end", payload: "processed" }
何がうれしいの?
A: 中間状態を綺麗に「外に出す」ことができる.
非同期処理を逐次的におこなうパターンを考える.
最終結果はもちろん大事だけど、中間状態にもしばしば利用価値がある.
例えばUIでプログレスバー(進捗状況、30%完了的な)を出すとか.
例えば通知を飛ばすとか("type:Afternoonまで終了しました"通知とか).
そして「一連の処理」と「中間状態の利用」が別の関心事の場合がよくある.
関心の分離/単一責任原則に則れば、一連の処理は一連の処理で閉じているべき.
しかしasync
/await
では途中結果をスマートに外に出す方法がない.
notify()
的な関数を引数にとって外部へメッセージングするしかない.
async function notBeautifulAsync(notify: Function) { notify({ type: "start", payload: "start processing" }); const step1 = await asyncProcessing(1); notify({ type: "processing", payload: "step1 finished" }); await asyncProcessing(step1); notify({ type: "processing", payload: "step2 finished" }); return { type: "end", payload: "processed" }; }
処理を書きたいだけなのに、よくわからんnotify関数を叩かなきゃいけない(それも返り値のない副作用の塊を)
そこで出てくるのがGenerator.
一定の区切りごとにyield
するだけで外部へメッセージングができる.
外部はyield
されてきた値の"段階"を知る術がない(step1が終わったメッセージなのかstep2なのかわからない)が、stepを考えないか、あるいは{type: "stepX", payload: "anything"}
をyield
するかで対応すればいい.
これで処理は処理のみを、中間状態の利用は利用のみを、関心を分離して扱える.