chokidar を Typescriptと仲良くさせる(fromEvent, async/await)
Typescriptの型チェックや、非同期処理を助けるasync/await、ObservableのfromEvent、utilのpromisifyなどにより、最近のライブラリは使いやすくなってきたと思う。
それでも、Javascriptでおなじみのコールバックやイベントエミッタを利用したライブラリとは仲良くしていかないといけない。例えばchokidar
は、インスタンス生成後ready
イベントまで待ってからでないと、add
やchange
イベントのリスナを追加できない厄介な性質がある。
正確には追加できるのだが、初期スキャンで全ファイルが「add」扱いにされてしまう。これは大抵の人にとって意図しない動作だと思う。
readyを待ってFSWatcherを返す
初期スキャンの完了(ready
イベント発生)してからのウォッチャ(FSWatcher
)が欲しければ、以下のようにする。もともとのwatch
関数の戻り値を得た後、ready
を待ってからresolve
で戻り値のFSWatcher
を返している。
function createWatcher( paths: string | string[], options: WatchOptions = {} ): Promise<FSWatcher> {
return new Promise( ( resolve, reject ) => {
try {
let watcher = watch( paths, options );
watcher.once( 'ready', () => resolve( watcher ) );
} catch( err ) {
reject( err );
}
} );
}
これで、以下のようにready
後のFSWatcher
が得られるようになる。
let watcher = await createWatcher( <パス> );
Observableを返すようにする
イベントのままだとObservable
と仲良くないので、rxjs
のfromEvent
を使ってObservable
化する。
fromEvent
を使う前に、いつイベントリスナが登録されるかというところが気になったので検証してみた。
検証: fromEventでリスナが増えるのはいつ?
FSWatcher
のインスタンスwatcher
に対して以下の操作を行ない、その間のリスナ数を調べてみた。
-
add
イベントを追加 - fromEventで
add
イベントをObservable化(add$と呼ぶ) - add$を購読(sb1と呼ぶ)
- add$を購読(sb2と呼ぶ)
- sb1の購読を停止
- sb2の購読を停止
// async関数内
let watcher = await createWatcher( <パス> );
console.log( '[0] listener: ' + watcher.listeners('add').length );
watcher.on( 'add', (path) => { console.log( '[on]: ' + path ) } );
console.log( '[1] listener: ' + watcher.listeners('add').length );
const add$ = fromEvent( watcher, 'add' );
console.log( '[2] listener: ' + watcher.listeners('add').length );
add$.subscribe( ( path ) => {
console.log( '[subscribe1]: ' + path );
} );
console.log( '[3] listener: ' + watcher.listeners('add').length );
add$.subscribe( ( path ) => {
console.log( '[subscribe2]: ' + path );
} );
console.log( '[4] listener: ' + watcher.listeners('add').length );
sb1.unsubscribe();
console.log( '[5] listener: ' + watcher.listeners('add').length );
sb2.unsubscribe();
console.log( '[6] listener: ' + watcher.listeners('add').length );
実行結果から、以下のように購読するごとにリスナが増えることが分かった。購読(subscribe)しない限りリスナが増えないというのは便利だ。
[0] listener: 0
[1] listener: 1
[2] listener: 1
[3] listener: 2
[4] listener: 3
[5] listener: 2
[6] listener: 1
検証: fromEventしてshareしたらどうなるか
先ほどの検証で、shareにしたらどうなるかを調べた。
let add$ = fromEvent( watcher, 'add' ).pipe( share() );
実行結果から、購読が増えてもイベントリスナは増えない = share
されていることが分かった。
[0] listener: 0
[1] listener: 1
[2] listener: 1
[3] listener: 2
[4] listener: 2 ← ここで購読が増えたが、リスナは増えない
[5] listener: 2
[6] listener: 1
困ったこと: 余計な情報が付いてくる
イベントリスナの場合、実はパスだけでなくファイルの情報を含んだ配列が生成されていることが分かった。推測だが[ path(paths), stat ]
になっていると思う。
fromEvent
だけだといらない情報が両方ついてきてしまって嬉しくないので、除外する必要がある。とりあえず戻り値の配列の[0]だけ取り出すようにする。
Observableを返す関数
これらを踏まえ、今どきのTypescriptによる型チェックを活用して使いやすくするなら以下のようになると思う。enum
にすることで、Eclipseなどで自動補間してくれるようになる。
import { FSWatcher } from 'chokidar';
import { fromEvent } from 'rxjs';
type EventType = 'add'
| 'change'
| 'unlink'
| 'addDir'
| 'unlinkDir';
function toObservable( watcher: FSWatcher, event: EventType ): Observable<string> {
return fromEvent( watcher, event ).pipe( map( info => info[0] ) );
}
備考
FSWatcher
インスタンスは、生成後にadd
で監視対象を増やすことができる。しかし、add
イベントリスナを追加済みの状態で監視対象を増やすと、初回スキャンで既存のファイルを追加したとみなしてしまうらしい。本当の追加と区別がつけられるなら良いが、Observableとは相性が良くなさそうだった。
Author And Source
この問題について(chokidar を Typescriptと仲良くさせる(fromEvent, async/await)), 我々は、より多くの情報をここで見つけました https://qiita.com/WeakenedPlayer/items/46b5c1d2e11008639a0c著者帰属:元の著者の情報は、元のURLに含まれています。著作権は原作者に属する。
Content is automatically searched and collected through network algorithms . If there is a violation . Please contact us . We will adjust (correct author information ,or delete content ) as soon as possible .