先日、DenoDB のモジュールの中身を調べていた。 調べていたら Deno の標準ライブラリのasync に突き当たった。 サンプルを見てピンと来なかったものがあったので、動作確認してみた。
参考 確認 delay promise 化した setTimeout のようになっている様子。
1 2 3 4 5 6 7 8 9 import { delay } from "https://deno.land/std/async/mod.ts" ;const startTime = Date .now ();await delay (2000 );let time = Date .now ();console .log (time - startTime);
中身を見ると、AbortSignal
対応していたので、自前で setTimeout を Promise 化するよりこちら使う方がよさげ。
debounce ? デバウンス? もともと電気電子系の用語らしい。 npm で公開されているlodash にも同じ名前で公開された関数があるそう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 import { debounce, delay } from "https://deno.land/std/async/mod.ts" ;let func = debounce ((value: string ) => console .log (`func(${value} )` ), 2000 );const startTime = Date .now ();let time = Date .now ();console .log (time - startTime);func ("f1" );await delay (1000 );time = Date .now (); console .log (time - startTime);func ("f2" );await delay (3000 ); time = Date .now (); console .log (time - startTime);func ("f3" );await delay (1000 );time = Date .now (); console .log (time - startTime);func ("f4" );
debounce
の第 2 引数で設定した時間以内に呼び出しされた場合には実行されない。 一定時間以内に呼び出しが無ければ第一引数で設定した関数が、最後に呼び出したときの引数に基づいて実行される。
「イベント処理の間引きに使う」という意味ピンと来なかったが動かしてみて意味がわかった。
deferred ? deferred は、英語で「延期」を意味する。 jQuery にも同様の名称の関数があった。
サンプル参考に確認。
1 2 3 4 5 import { deferred } from "https://deno.land/std/async/mod.ts" ;const p = deferred<number >();p.resolve (42 );
何もわからない。 ということで、もう少し書いてみる。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import { deferred, type Deferred } from "https://deno.land/std/async/mod.ts" ;const p = deferred<number >();const func = async (d: Deferred<number > ) => { console .log ("Wait start" ); const src = await d; console .log ("Wait end" ); console .log (src); }; console .log ("A" );func (p);console .log ("B" );p.resolve (42 ); console .log ("C" );
実行結果は次のようになる。
1 2 3 4 5 6 7 $ deno run async.ts A Wait start B C Wait end 42
ここまで作って理解。 「延期」の名の通り、deferred()
を引数に渡して実際の引数設定を延期させることができた。
ちなみに、p.reject(42);
と書くと error: Uncaught (in promise)
とエラーになる。
MuxAsyncIterator 複数のストリームを、多重化できる。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import { MuxAsyncIterator } from "https://deno.land/std/async/mod.ts" ;async function * gen123 (): AsyncIterableIterator <number > { yield 1 ; yield 2 ; yield 3 ; } async function * gen456 (): AsyncIterableIterator <number > { yield 4 ; yield 5 ; yield 6 ; } const mux = new MuxAsyncIterator <number >();mux.add (gen123 ()); mux.add (gen456 ()); for await (const value of mux) { console .log (value); }
実行結果は次の通り。
1 2 3 4 5 6 7 $ deno run async.ts 1 4 2 5 3 6
2つのストリームの実行結果が交互に混ざった形で、取得できる 多重化?と感じたが交互に出てくるということだった。 3 つなら 3 つ多重化できた。
pooledMap (非同期の)イテレーターを、非同期のイテレータに変換できる関数。
サンプルを見るとピンとこないので、先にソースを見る。
pooledMap の実装から抜粋 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 export function pooledMap<T, R>( poolLimit : number , array : Iterable <T> | AsyncIterable <T>, iteratorFn : (data: T ) => Promise <R> ): AsyncIterableIterator <R> { }
これは正直サンプル見ただけでは、意味不明。 第二引数に渡したイテレータを、第一引数にで定義した値だけ同時に処理(おそらく遅延なく連続して実行が正しい?)し、 第三引数で定義した関数の記述に基づいて新しい非同期イテレーターとしてふるまうようです。
ここでサンプルを確認。
1 2 3 4 5 6 7 8 9 10 11 import { pooledMap } from "https://deno.land/std/async/mod.ts" ;const results = pooledMap ( 2 , [1 , 2 , 3 ], (i ) => new Promise ((r ) => setTimeout (() => r (i), 1000 )) ); for await (const value of results) { console .log (value); }
実行すると次の通り。
1 2 3 4 $ deno run async.ts 1 2 <=ここまで連続して処理され、1秒停止 3
一定間隔で処理しつつ、その間隔で複数個処理するパターンが組めるようで有れば便利に使えそうです。 例えば、数秒に 1 回 3 種類のパラメータで API にアクセスするとか。
tee シェルコマンドの tee をイメージしたが、意味合いとしては同じくしていた。 ストリームの出力先を分割できた。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import { tee } from "https://deno.land/std/async/tee.ts" ;const gen = async function * gen ( ) { yield 1 ; yield 2 ; yield 3 ; }; const [branch1, branch2] = tee (gen ());await (async () => { for await (const n of branch1) { console .log (n); } })(); await (async () => { for await (const n of branch2) { console .log (n); } })();
実行すると次のようになる。
ループに書いてある、トップレベル await を外すと、2つの処理での出力が交錯する。
ジェネレーターで作成された値を、複数の処理で使いたい。 が、その複数の処理自体には直接関連性がない時、同じループで処理するのにモヤっとしてた。 今後これは使いそう。(時刻とかが絡まなければ)
deadline 設定した時間以内に処理できない場合エラーを起こすことが deadline。
1 2 3 4 5 6 7 8 9 10 import { deadline, delay } from "https://deno.land/std/async/mod.ts" ;const delayedPromise = async ( ) => { await delay (1000 ); return 10 ; }; const result = await deadline (delayedPromise (), 10 );console .log (result);
実行すると次のようになる。
1 2 3 4 5 6 7 $ deno run async.ts error: Uncaught (in promise) DeadlineError: Deadline const t = setTimeout(() => d.reject(new DeadlineError()), delay); ^ at https://deno.land/std@0.123.0/async/deadline.ts:15:39 at Object.action (deno:ext/timers/01_timers.js:161:11) at handleTimerMacrotask (deno:ext/timers/01_timers.js:78:12)
第二引数 10 = 10ms 以内に、第一引数の結果が受け取れなかったからエラーになったというもの 時間を伸ばすと、resultに結果が返ってくるようになる。
1 2 3 4 5 6 7 8 9 10 11 import { deadline, delay } from "https://deno.land/std/async/mod.ts" ;const delayedPromise = async ( ) => { await delay (1000 ); return 10 ; }; const result = await deadline (delayedPromise (), 2000 );console .log (result);
throw されているので、エラー処理はもちろんできる。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 import { deadline, delay } from "https://deno.land/std/async/mod.ts" ;const delayedPromise = async ( ) => { await delay (1000 ); return 10 ; }; let result : undefined |number try { result = await deadline (delayedPromise (), 10 ); } catch (e) { console .log (e); result = 0 ; } console .log (result);
というわけで、Deno の async のモジュールを一通り見てみた。 delayを筆頭に、存在を知らないとクオリティーを下げた自作してしまうようなものが多かったと感じる。
ではでは。