【javascript応用編】中断もできてasync/awaitも使える再帰setTimeoutループ処理のPromise版の自作するタコつぼの中でプログラミングをコツコツ楽しむ人のためのやや偏ったブログです。当ページには【広告/PR】を含む場合があります。まとめ以前の別記事で、setTimeoutを再帰的に利用した一定時間間隔でループする処理について紹介したことがあります。【Angular活用講座】Rxjs:repeatオペレーターで一定時間間隔の処理(再帰的ループ)を行わせてみるRxjsで再帰的なループ処理を実行するためのrepeatオペレーターのデザインパターンを紹介します。今回は標準のsetTimeout関数単体では用途的に不十分だった、「タイマーの停止」や「await/async可能な非同期処理」に対応したPromiseベースの一定時間毎に定期実行されるループ処理の処理の実装方法について考えてみます。Javascript(js)&Typescript(ts)プログラミング入門〜これから学ぶ人のためのおすすめ書籍&教材の手引き課題の再確認〜標準のsetTimeout関数は「非同期割り込み」できないまず以前のブログ記事で紹介していた「再帰したsetTimeout関数」について、今回の問題点となるsetTimeout関数の内部でPromiseの呼び出しがどのように振る舞うか、少し復習します。javascripで一定時間間隔で定期的な処理をテクニックとして、以下の方法を紹介していました。ms);javascriptの標準APIの一つであるsetTimeout関数にはいくつかのオーバーロードタイプが存在しますが、基本形は第一引数がコールバック関数、第二引数が待機時間時間になります。コールバック関数をasync指定にしておけば、awaitが使えて、その間はタイマーも停まり、元の待機時間が更に遅延してくれるように思えます。ms);実際のところ、これでもたまたま問題なく処理されるときもあれば、意図しない挙動で制御不能になるときもあり、それがこの問題を分かりにくく、理解の難しいものにしています。ここでのタイマーはグローバルスコープでtimerIdをインスタンス化しているので、setTimeoutが待機してくれるのはメインプロセスで走っているグローバル時間になります。他方で、setTimeoutのコールバックから非同期処理であるPromise処理を呼び出したら、そこからは新たに別のサブ(子)プロセスが走るため、awaitが処理待ちするのは、サブプロセス上の別の時間になっています。この場合、通常のsetTimeout関数のコールバック関数の中でasync/await構文を使おうとすると、予想のつかない滅茶苦茶な処理になってしまう恐れがあります。この点を確認するため、以下のようなソースコード・standard_loop.jsでこの挙動を検証してみましょう。10000);})();このコードを利用して、何度か動かしてみます。とある実行結果で、タイマーが上手く止まるときの出力は以下のようになっています。16.24s.しかし、また別の実行結果では、以下のようにタイマー終了後もコールバックの処理が止まっていないため、プログラムが暴走してしまいます。見てのように、サブプロセス(コールバックの内部処理)が非同期処理中の間に、メインプロセス側(外部)からタイマーを停止させたつもりになっていても、実際には「タイマーが正常に止まってくれる保証がない」、ということが問題になってきます。Javascript(js)&Typescript(ts)プログラミング入門〜これから学ぶ人のためのおすすめ書籍&教材の手引き非同期割り込みにも使える再帰setTimeoutタイマーのPromise対応させるでは、先程掲げていた問題の解決策として、いかなるときでもタイマーを正常に終了させるためには、「処理中のサブプロセスも全て終了させてから、タイマーを止める」ことです。これには、Promiseの機能を上手く利用する必要が出てきます。なると、標準のsetTimeout関数だけでは機能が不十分ですので、setTimeoutメソッドもPromiseを使って大幅な改造を施さないといけません。先程のスタンダードなsetTimeoutの再帰的処理をPromiseへ書き換えると、以下のようなソースコード・promise_loop.jsで置き換えることができます。10000);})();これを実行すると、如何なる場合であれ、確実に内部の非同期処理もろとも終了させることが可能になっていると思います。内部処理でタイマーも終了とはいえ、標準のsetTimeoutを再帰的に利用した繰り返しループ処理よりも、その仕組みは非常に難関に思えます。今回のPromise版ループ処理のキモは、ソースコード中のsubscriber関数の中身をきちんと理解できているかどうかにかかっています。Promise特有の実装パターンに慣れるまでは、なかなか理解しにくいものですが、その根底にある考え方に『メソッドチェーン』というものがあります。これは任意のPromiseインスタンスは、thenメソッドを使って別のPromiseインスタンスへ繋ぐことで、解決(resolve)した値を繋いだPromise側へ送り出すことができます。基本的にこのthenメソッドを使って、Promiseから他のPromiseへいくらでも接続できるので、これをメソッドチェーンと呼んでいます。一つのPromiseインスタンスは、内部の処理が完了することで、引数のresolveを通して、その返り値にまた新しいPromiseインスタンスを返します。内部の処理が正常に完了せずに、エラーが発生した場合、引数のrejectを通して、エラーが起こった理由の情報を保持したPromiseインスタンスが返ります。...ということで、話を元に戻して、subscriber関数を良くみてもらうと、「Promiseを返す関数を返すPromiseを返す関数」になっています。日本語にすると複雑ですが、「メソッドチェーン」を理解していたらさほど難しくはなく、「処理を続ける場合はメソッドチェーンで別のPromiseへ繋ぎ、処理を終わる場合は新たなチェーンに繋がない」、ということをやっています。慣れないうちは、Promiseのメソッドチェーンを再帰的に呼び出すのは、非常に混乱しやすい使い方なのは間違いありません。Javascript(js)&Typescript(ts)プログラミング入門〜これから学ぶ人のためのおすすめ書籍&教材の手引きまとめ以上、機能を拡張するためにsetTimeout関数ベースにして、内部で非同期処理も利用できる再帰的ループをPromise対応にしてみました。ここまで見てきたようにPromise版の再帰的なループ処理は非常に分かりにくく、実装するには少々複雑な構造にように感じます。setTimeoutを使った再帰ループ処理は、単純な構造ゆえに内部処理を理解しやすい、というメリットは失われ、もはや別モノの処理です。話のネタとしては興味深いものでしたが、今回のようにタイマーを用いて複雑な処理を行う必要があるのなら、Rxjsのような時間的なイベント処理を制御するものに特化した外部ライブラリを用いるのが良いと思います。以前の記事で取り上げた『Rxjsを使ったWhileループ処理的なストリームの簡潔な設計方法』を紹介していますので、興味があればそちらとも比較してみてください。【Angular活用講座】Rxjs:repeatオペレーターで一定時間間隔の処理(再帰的ループ)を行わせてみるRxjsで再帰的なループ処理を実行するためのrepeatオペレーターのデザインパターンを紹介します。【Angular活用講座】Rxjs:repeatオペレーターで一定時間間隔の処理(再帰的ループ)を行わせてみるAngularアプリ開発で知っていると使えるかもしれないRxjsで再帰的なループ処理を実行するためのrepeatオペレーターのデザインパターンを紹介します。async関数TypescriptのInterfaceでasync関数を定義する・async関数でクラス変数(this)を使うTypescriptでinterfaceやclassからasync/await関数を付けて使いたいときの注意点を簡単に解説していきます。interfaceTypeScriptのInterfaceでStaticなクラスメソッドを適用させる方法Typescriptで使うinterfaceからでもStaticなメンバーメソッドを実装するクラスに型強制出来るように対応させるためのテクニックを紹介します。記事の担当:taconocatナンデモ系エンジニア主にAngularでフロントエンド開発することが多いです。開発環境はLinuxメインで進めているので、シェルコマンドも多用しております。プログラミングするのが好きな人間です。