graphql-subscription を使ってみる - まず PubSub って何
apollographql/graphql-subscriptions を使ってみたくて README の通りのコードを書いて理解しようと思ったのですが、PubSub
やら AsyncIterator
やら軽く聞いたことはあるもののよく理解していない用語だらけでちんぷんかんぷんでした。なので GraphQL の resolver
と絡める前にもっと単純な例で試してみようと、コードを書いて試してみました。
以下のコードでは PubSub のインスタンスから直接 publish
を定期的に叩くものです。
import { PubSub } from 'graphql-subscriptions' const pubsub = new PubSub() // トリガ名(メッセージの種類を決めるイベントハンドラ名みたいなもの)を指定して // AsyncIterator というなんか難しいものを作成する const iterator = pubsub.asyncIterator( 'something_changed') // 500ms ごとに publish する setInterval(() => { pubsub.publish( 'something_changed', { somethingChanged: { id: '123' } }) }, 500) // とりあえず 10 回読み取ってみる (*˘꒳˘*) 残りはしらない for (let i = 0; i < 10; i++) { iterator.next().then(console.log) }
これで
{ value: { somethingChanged: { id: '123' } }, done: false } { value: { somethingChanged: { id: '123' } }, done: false } :
のようなメッセージが10回表示されます。
AsyncIterator
についてはまだよくわかっていないのですが、Promise(async/await)
と for...of
を組み合わせられるすごい何か... なんですよね。単純に GraphQL の Subscription を試してみたかっただけなのですが、気をぬくと横道にどんどん逸れていくといういつものパターン...
AsyncIterator
AsyncIterator について簡単に眺めてみようと TypeScript の定義を見ると
interface AsyncIterator<T> { next(value?: any): Promise<IteratorResult<T>>; return?(value?: any): Promise<IteratorResult<T>>; throw?(e?: any): Promise<IteratorResult<T>>; }
となっています。TypeScript は v2.3 から AsyncIterator に対応しており for..await..of
記法を利用できるみたいです。Iterator が for...of
で用いられるので、それの非同期版ですね。
next()
メソッドを呼ぶことでイテレーション中の次の対象を得ることができるのは通常の Iterator と同じで、これだけを見ると返り値が Promise
になっているだけの Iterator です。
しかし上のコードを for..await..of
で書き換えて確認してみたところ、どうやら際限なくイテレーションが行えるようです。かなり Publish/Subscribe 感のある振る舞いですね。
// 怒られるのでシンボルを定義 (Symbol as any).asyncIterator = Symbol.for('Symbol.asyncIterator') import { PubSub } from 'graphql-subscriptions' const pubsub = new PubSub() const iterator = pubsub.asyncIterator('something_changed') // 500ms ごとに publish する setInterval(() => { pubsub.publish('something_changed', { somethingChanged: { id: '123' } }) }, 500); // イテレーションで無限に読み取れるようになった (*˘꒳˘*) しあわせ (async () => { for await (const x of iterator) { console.log(x) } })()
TypeScript のドキュメントにあるように Synbol.asyncIterator
が未定義であることによるエラーが発生するので
(Symbol as any).asyncIterator = Symbol.for("Symbol.asyncIterator")
を追記しています。参考までに実行時の tsconfig.json
をメモしておきます。
{ "compilerOptions": { "target": "es2017", "lib": ["esnext"], }
イテレータやシンボルなど、今まで知らなくてもどうにかなってきたことがここにきて理解の足かせになってきたので、そろそろちゃんとしないとな... と思ったのでした。
なんにせよ、これで PubSub
の大体の振る舞いはつかめたので、次は GraphQL の Subscription として使ってみたいところです。