如何使用事件发射器作为异步生成器

时间:2018-06-26 14:17:41

标签: node.js asynchronous events iterator generator

我正在尝试将异步生成器的简洁语法与babel配合使用(我被困在节点8上),我想知道如何将事件发射器干净地转换为异步生成器

到目前为止,我得到的是这样的

    const { EventEmitter } = require('events')

    // defer fonction for resolving promises out of scope
    const Defer = () => {
      let resolve
      let reject
      let promise = new Promise((a, b) => {
        resolve = a
        reject = b
      })
      return {
        promise,
        reject,
        resolve
      }
    }


    // my iterator function
    function readEvents(emitter, channel) {
      const buffer = [Defer()]
      let subId = 0

      emitter.on(channel, x => {
        const promise = buffer[subId]
        subId++
        buffer.push(Defer())
        promise.resolve(x)
      })

      const gen = async function*() {
        while (true) {
          const val = await buffer[0].promise
          buffer.shift()
          subId--
          yield val
        }
      }

      return gen()
    }

    async function main () {
      const emitter = new EventEmitter()
      const iterator = readEvents(emitter, 'data')

      // this part generates events
      let i = 0
      setInterval(() => {
        emitter.emit('data', i++)
      }, 1000)

      // this part reads events
      for await (let val of iterator) {
        console.log(val)
      }
    }

    main()

这是很多IMO的方法,所以我问是否有人对简化此想法有所了解

3 个答案:

答案 0 :(得分:1)

这里是另一种看法,使用 for await 循环、自定义 Symbol.asyncIterator 和用于任何潜在事件缓冲的简单队列处理计时器滴答事件。适用于 Node 和浏览器环境(RunKitGist)。

async function main() {
  const emitter = createEmitter();
  const start = Date.now();

  setInterval(() => emitter.emit(Date.now() - start), 1000);

  for await (const item of emitter) {
    console.log(`tick: ${item}`);
  }
}

main().catch(e => console.warn(`caught on main: ${e.message}`));

function createEmitter() {
  const queue = [];
  let resolve;

  const push = p => {
    queue.push(p);
    if (resolve) {
      resolve();
      resolve = null;
    }
  };

  const emitError = e => push(Promise.reject(e));

  return {
    emit: v => push(Promise.resolve(v)),
    throw: emitError,

    [Symbol.asyncIterator]: () => ({
      next: async () => {
        while(!queue.length) {
          await new Promise((...a) => [resolve] = a);
        }
        return { value: await queue.pop(), done: false };
      },
      throw: emitError
    })
  };
}

答案 1 :(得分:0)

假设我们使用redux-saga(因为它的核心使用generator)和socket.io作为EventEmitter的示例

import { call, put } from 'redux-saga/effects';

function* listen() {
  yield (function* () {
    let resolve;
    let promise = new Promise(r => resolve = r); // The defer

    socket.on('messages created', message => {
      console.log('Someone created a message', message);
      resolve(message); // Resolving the defer

      promise = new Promise(r => resolve = r); // Recreate the defer for the next cycle
    });

    while (true) {
      const message = yield promise; // Once the defer is resolved, message has some value
      yield put({ type: 'SOCKET_MESSAGE', payload: [message] });
    }
  })();
}

export default function* root() {
    yield call(listen);
}

以上设置应为您提供了一个生成器,该生成器被事件发射器(socket.io实例)发射的下一个事件阻止。

干杯!

答案 2 :(得分:0)

我想到了这个

async *stream<TRecord extends object=Record<string,any>>(query: SqlFrag): AsyncGenerator<TRecord> {
    const sql = query.toSqlString();

    let results: TRecord[] = [];
    let resolve: () => void;
    let promise = new Promise(r => resolve = r);
    let done = false;

    this.pool.query(sql)
        .on('error', err => {
            throw err;
        })
        .on('result', row => {
            resolve();
            results.push(row);
            promise = new Promise(r => resolve = r);
        })
        .on('end', () => {
            done = true;
        })

    while(!done) {
        await promise;
        yield* results;
        results = [];
    }
}

到目前为止似乎仍在工作。

即您可以像在Khanh的解决方案中那样创建一个虚拟的Promise,以便可以等待第一个结果,但是随后由于许多结果可能一次全部出现,因此将它们推入数组并重置Promise以等待结果(或一批)结果)。不管这个承诺是否在其等待之前被重写数十次都没有关系。

然后,我们可以使用yield*一次产生所有结果,并刷新数组以进行下一批处理。