17

redux-sagaを使用してPouchDBからReact.jsアプリケーションにイベントを接続しようとしていますが、PouchDB から発行されたイベントを Saga に接続する方法を理解するのに苦労しています。イベントはコールバック関数を使用するため (ジェネレーターを渡すことはできません)、yield put()コールバック内で使用することはできません。ES2015 のコンパイル (Webpack を使用) 後に奇妙なエラーが発生します。

これが私が達成しようとしていることです。動作しない部分は内部にありreplication.on('change' (info) => {})ます。

function * startReplication (wrapper) {
  while (yield take(DATABASE_SET_CONFIGURATION)) {
    yield call(wrapper.connect.bind(wrapper))

    // Returns a promise, or false.
    let replication = wrapper.replicate()

    if (replication) {
      replication.on('change', (info) => {
        yield put(replicationChange(info))
      })
    }
  }
}

export default [ startReplication ]
4

5 に答える 5

27

Nirrek が説明したように、データ ソースをプッシュするために接続する必要がある場合、そのソースのイベント イテレータを作成する必要があります。

上記のメカニズムを再利用可能にすることができることを付け加えたいと思います。そのため、異なるソースごとにイベント イテレータを再作成する必要はありません。

解決策は、およびメソッドを使用して汎用チャネルを作成することです。Generator 内からメソッドを呼び出して、メソッドをデータ ソースのリスナー インターフェイスに接続できます。puttaketakeput

これが可能な実装です。メッセージを待っている人がいない場合、チャネルはメッセージをバッファリングすることに注意してください (たとえば、ジェネレータがリモート呼び出しでビジー状態である場合)。

function createChannel () {
  const messageQueue = []
  const resolveQueue = []

  function put (msg) {
    // anyone waiting for a message ?
    if (resolveQueue.length) {
      // deliver the message to the oldest one waiting (First In First Out)
      const nextResolve = resolveQueue.shift()
      nextResolve(msg)
    } else {
      // no one is waiting ? queue the event
      messageQueue.push(msg)
    }
  }

  // returns a Promise resolved with the next message
  function take () {
    // do we have queued messages ?
    if (messageQueue.length) {
      // deliver the oldest queued message
      return Promise.resolve(messageQueue.shift())
    } else {
      // no queued messages ? queue the taker until a message arrives
      return new Promise((resolve) => resolveQueue.push(resolve))
    }
  }

  return {
    take,
    put
  }
}

その後、上記のチャネルは、外部のプッシュ データ ソースをリッスンしたいときにいつでも使用できます。あなたの例では

function createChangeChannel (replication) {
  const channel = createChannel()

  // every change event will call put on the channel
  replication.on('change', channel.put)
  return channel
}

function * startReplication (getState) {
  // Wait for the configuration to be set. This can happen multiple
  // times during the life cycle, for example when the user wants to
  // switch database/workspace.
  while (yield take(DATABASE_SET_CONFIGURATION)) {
    let state = getState()
    let wrapper = state.database.wrapper

    // Wait for a connection to work.
    yield apply(wrapper, wrapper.connect)

    // Trigger replication, and keep the promise.
    let replication = wrapper.replicate()

    if (replication) {
      yield call(monitorChangeEvents, createChangeChannel(replication))
    }
  }
}

function * monitorChangeEvents (channel) {
  while (true) {
    const info = yield call(channel.take) // Blocks until the promise resolves
    yield put(databaseActions.replicationChange(info))
  }
}
于 2016-02-09T09:56:00.313 に答える
1

PouchDB を使用しても同じ問題が発生し、回答が非常に有用で興味深いものであることがわかりました。ただし、PouchDB で同じことを行う方法はたくさんあります。私は少し掘り下げて、推論しやすい別のアプローチを見つけました。

リクエストにリスナーをアタッチしない場合db.change、変更データは呼び出し元に直接返さcontinuous: trueれ、オプションに追加すると longpoll が発行され、何らかの変更が発生するまで返されません。したがって、次のようにして同じ結果を得ることができます

export function * monitorDbChanges() {
  var info = yield call([db, db.info]); // get reference to last change 
  let lastSeq = info.update_seq;

  while(true){
    try{
      var changes = yield call([db, db.changes], { since: lastSeq, continuous: true, include_docs: true, heartbeat: 20000 });
      if (changes){
        for(let i = 0; i < changes.results.length; i++){
          yield put({type: 'CHANGED_DOC', doc: changes.results[i].doc});
        }
        lastSeq = changes.last_seq;
      }
    }catch (error){
      yield put({type: 'monitor-changes-error', err: error})
    }
  }
}

底に達していないことが1つあります。forループを次のように置き換えるとchange.results.forEach((change)=>{...})、で無効な構文エラーが発生しますyield。イテレータの使用における衝突と関係があると思います。

于 2016-04-29T13:46:46.777 に答える