超速で非同期処理のキャンセルを組み込む with Rx


ng-japan meetup 2017 Autumn (2017年10月10日)

LT発表資料


名前: ちきさん
所属: オプト
GitHub/Twitter/Qiita: @ovrmrw

We are hiring!


JSBin

StackBlitz


よくありそうなユーザーストーリー


引数valueを受けて、非同期処理をした後に処理結果をObservable型で返す関数があったとして、

function httpRequest$(value) {
  const promise = new Promise(resolve => {
    setTimeout(() => {
      resolve(value + '!') // valueに "!" を付けて返す
    }, Math.random() * 200) // timeoutはランダムで変わる
  })
  return Observable.from(promise)
}

subscribeしてその処理結果を使って何かしたいとする。

function log(result) {
  console.log(result) // ここではコンソールに出力しているだけ
}

例えばキーワードが入力される度にHTTPリクエストが走る入力フォームがあったとして、

httpRequest$('a').subscribe(log)
httpRequest$('ab').subscribe(log)
httpRequest$('abc').subscribe(log)

レスポンスは毎回同じ順番で来るとは限らないので、

"ab!"
"a!"
"abc!"

こうなったり

"abc!"
"ab!"
"a!"

こうなったりする。


でもこのときに欲しい結果は "abc!" だけだったりします。

今回はそういうケースでどうすればさくっと解決できるのか、というお話。


Subject と switchMap


前述のユーザーストーリーでは、要するに最後のリクエストより前のリクエスト(非同期処理)はキャンセルしてしまえば良いので、


まずSubjectを用意する。

const dispatcher$ = new Subject()
const provider$ = new Subject()

それぞれの役目

  • dispatcher ... ストリームに引数valueを流す
  • provider ... 別のストリームに処理後の値を流す

dispatcherから始まるストリームではswitchMapで後続の非同期処理をキャンセルしつつ、処理結果をproviderのストリームに流す。

// Serviceのconstructorとかで
Observable
  .from(dispatcher$)
  .switchMap(value => {
    return httpRequest$(value) // 非同期処理をswitchMapの中に持ってくる
  })
  .subscribe(result => {
    provider$.next(result)
  })

そしてprovidersubscribeして何らかの処理をする。

// Serviceのどこかで
provider$
  .subscribe(log)

先ほどは↓こうだったリクエストの発行が、

httpRequest$('a').subscribe(log)
httpRequest$('ab').subscribe(log)
httpRequest$('abc').subscribe(log)

↓このように変わるだけ。

dispatcher$.next('A')
dispatcher$.next('AB')
dispatcher$.next('ABC')

先ほどは↓こうだったレスポンスが、

"abc!"
"a!"
"ab!"

↓このように変わる。

"ABC!"

("A!""AB!"は非同期処理がキャンセルされたので出力もされない)


そんなに手間をかけずに非同期処理キャンセルの仕組みが作れました。

JSBinで実際の動きを確認できます。 https://jsbin.com/vevukev/3/edit?js,console

StackBlitzで実際に組み込んだAngularアプリを編集できます。 https://stackblitz.com/edit/angular-wjrath


Thanks