Javascript 如何将发出的事件与redux传奇联系起来?

Javascript 如何将发出的事件与redux传奇联系起来?,javascript,ecmascript-6,redux,redux-saga,Javascript,Ecmascript 6,Redux,Redux Saga,我试图使用将事件从连接到我的应用程序,但我很难弄清楚如何将从PockDB发出的事件连接到我的传奇。由于事件使用回调函数(我无法将其传递给生成器),因此我无法在回调中使用yield put(),在ES2015编译(使用Webpack)后会出现奇怪的错误 这就是我想要完成的,不起作用的部分是在replication.on('change'(info)=>{})中 我们必须解决的基本问题是事件发射器是“基于推”的,而传奇故事是“基于拉”的 如果订阅了这样的事件:replication.on('chan

我试图使用将事件从连接到我的应用程序,但我很难弄清楚如何将从PockDB发出的事件连接到我的传奇。由于事件使用回调函数(我无法将其传递给生成器),因此我无法在回调中使用
yield put()
,在ES2015编译(使用Webpack)后会出现奇怪的错误

这就是我想要完成的,不起作用的部分是在
replication.on('change'(info)=>{})


我们必须解决的基本问题是事件发射器是“基于推”的,而传奇故事是“基于拉”的

如果订阅了这样的事件:
replication.on('change',(info)=>{})
,则每当
复制
事件发射器决定推送一个新值时,就会执行回调

对于传奇,我们需要改变控制。这是一个传奇故事,它必须控制何时决定对新的变化信息作出反应。换句话说,一部传奇需要获取新的信息

下面是实现这一点的一个方法示例:

函数*startReplication(包装器){
while(收益率(数据库设置配置)){
屈服应用(包装,包装,连接);
让replication=wrapper.replicate()
如果(复制)
产量调用(monitorChangeEvents、复制);
}
}
函数*monitorChangeEvents(复制){
const stream=createReadableStreamOfChanges(复制);
while(true){
const info=yield stream.read();//阻塞,直到承诺解析为止
收益率卖出(复制变化(信息));
}
}
//返回一个流对象,该流对象具有可用于读取新信息的read()方法。
//read()方法返回一个承诺,当来自
//更改事件变为可用。这就是让我们从工作中解脱出来的原因
//将“基于推”的模型转换为“基于拉”的模型。
函数createReadableStreamOfChanges(复制){
让我们推迟;
replication.on('change',info=>{
如果(!延期)返回;
延迟。解决(信息);
延迟=空;
});
返回{
读(){
如果(延期)
回报。承诺;
延迟={};
deferred.promise=新承诺(resolve=>deferred.resolve=resolve);
回报。承诺;
}
};
}
这里有一个以上示例的JSbin:

你还应该看看Yassine Elouafi对类似问题的回答:

正如Nirrek所解释的,当您需要连接到推送数据源时,您必须为该源构建一个事件迭代器

我想补充一点,上面的机制可以重用。因此,我们不必为每个不同的源重新创建事件迭代器

解决方案是使用
put
take
方法创建一个通用频道。您可以从生成器内部调用
take
方法,并将
put
方法连接到数据源的侦听器接口

下面是一个可能的实现。请注意,如果没有人在等待消息,则通道会缓冲消息(例如,生成器正忙于进行一些远程呼叫)

函数createChannel(){
const messageQueue=[]
const resolveQueue=[]
函数put(msg){
//有人在等留言吗?
if(resolveQueue.length){
//将邮件传递给等待的最老邮件(先进先出)
const nextResolve=resolveQueue.shift()
nextResolve(msg)
}否则{
//没有人在等待?将事件排队
messageQueue.push(消息)
}
}
//返回与下一条消息一起解析的承诺
函数take(){
//我们有排队的消息吗?
if(messageQueue.length){
//传递最早的排队消息
返回Promise.resolve(messageQueue.shift())
}否则{
//没有排队的消息?将接收者排队,直到消息到达
返回新承诺((解析)=>resolveQueue.push(解析))
}
}
返回{
拿
放
}
}
然后,您可以随时使用上述通道收听外部推送数据源。以你为例

函数createChangeChannel(复制){
const channel=createChannel()
//每个变更事件都会在频道上播放
replication.on('change',channel.put)
返回通道
}
函数*startReplication(getState){
//等待配置设置。这可能会发生多次
//生命周期中的时间,例如,当用户希望
//切换数据库/工作区。
while(收益率(数据库设置配置)){
let state=getState()
让wrapper=state.database.wrapper
//等待连接工作。
屈服应用(包装器,包装器,连接)
//触发复制,并遵守承诺。
让replication=wrapper.replicate()
如果(复制){
产量调用(monitorChangeEvents、createChangeChannel(复制))
}
}
}
函数*监视器更改事件(通道){
while(true){
const info=yield call(channel.take)//阻塞,直到承诺解决为止
产出put(databaseActions.replicationChange(info))
}
}

感谢@Yassine Elouafi

我基于@Yassine Elouafi的解决方案,为TypeScript语言创建了简短的MIT许可通用通道实现,作为redux saga扩展

// redux-saga/channels.ts
import { Saga } from 'redux-saga';
import { call, fork } from 'redux-saga/effects';

export interface IChannel<TMessage> {
    take(): Promise<TMessage>;
    put(message: TMessage): void;
}

export function* takeEvery<TMessage>(channel: IChannel<TMessage>, saga: Saga) {
    while (true) {
        const message: TMessage = yield call(channel.take);
        yield fork(saga, message);
    }
}

export function createChannel<TMessage>(): IChannel<TMessage> {
    const messageQueue: TMessage[] = [];
    const resolveQueue: ((message: TMessage) => void)[] = [];

    function put(message: TMessage): void {
        if (resolveQueue.length) {
            const nextResolve = resolveQueue.shift();
            nextResolve(message);
        } else {
            messageQueue.push(message);
        }
    }

    function take(): Promise<TMessage> {
        if (messageQueue.length) {
            return Promise.resolve(messageQueue.shift());
        } else {
            return new Promise((resolve: (message: TMessage) => void) => resolveQueue.push(resolve));
        }
    }

    return {
        take,
        put
    };
}
//redux saga/channels.ts
从“redux Saga”导入{Saga};
从“redux saga/effects”导入{call,fork};
导出接口IChannel{
承诺;
put(message:TMessage):void;
}
导出功能*takeEvery(频道:IChannel,传奇:传奇){
while(true){
const message:TMessage=yield call(channel.take);
屈服叉(传奇故事、消息);
}
}
导出函数createChannel():i通道{
const messageQueue:TMessage[]=[];
const resolveQueue:((消息:TMessage)=>void)[]=];
函数put(消息:TMessage):void{
如果(解决方案)
// redux-saga/channels.ts
import { Saga } from 'redux-saga';
import { call, fork } from 'redux-saga/effects';

export interface IChannel<TMessage> {
    take(): Promise<TMessage>;
    put(message: TMessage): void;
}

export function* takeEvery<TMessage>(channel: IChannel<TMessage>, saga: Saga) {
    while (true) {
        const message: TMessage = yield call(channel.take);
        yield fork(saga, message);
    }
}

export function createChannel<TMessage>(): IChannel<TMessage> {
    const messageQueue: TMessage[] = [];
    const resolveQueue: ((message: TMessage) => void)[] = [];

    function put(message: TMessage): void {
        if (resolveQueue.length) {
            const nextResolve = resolveQueue.shift();
            nextResolve(message);
        } else {
            messageQueue.push(message);
        }
    }

    function take(): Promise<TMessage> {
        if (messageQueue.length) {
            return Promise.resolve(messageQueue.shift());
        } else {
            return new Promise((resolve: (message: TMessage) => void) => resolveQueue.push(resolve));
        }
    }

    return {
        take,
        put
    };
}
// example-socket-action-binding.ts
import { put } from 'redux-saga/effects';
import {
    createChannel,
    takeEvery as takeEveryChannelMessage
} from './redux-saga/channels';

export function* socketBindActions(
    socket: SocketIOClient.Socket
) {
    const socketChannel = createSocketChannel(socket);
    yield* takeEveryChannelMessage(socketChannel, function* (action: IAction) {
        yield put(action);
    });
}

function createSocketChannel(socket: SocketIOClient.Socket) {
    const socketChannel = createChannel<IAction>();
    socket.on('action', (action: IAction) => socketChannel.put(action));
    return socketChannel;
}
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})
    }
  }
}
// fetch history messages
function* watchMessageEventChannel(client) {
  const chan = eventChannel(emitter => {
    client.on('message', (message) => emitter(message));
    return () => {
      client.close().then(() => console.log('logout'));
    };
  });
  while (true) {
    const message = yield take(chan);
    yield put(receiveMessage(message));
  }
}

function* fetchMessageHistory(action) {
  const client = yield realtime.createIMClient('demo_uuid');
  // listen message event
  yield fork(watchMessageEventChannel, client);
}