如何在nodejs流

时间:2017-11-24 07:41:08

标签: javascript node.js asynchronous stream

最近我开始思考 - 我是否以正确的方式处理Nodejs流中的异步操作?因此,我只是想确保它是这样。

class AsyncTransform extends Transform {
   constructor() { 
      super({objectMode: true});
   }

   public async _transform(chunk, enc, done) {
      const result = await someAsyncStuff();
      result && this.push(result);
      done();
   }
}

这个小例子效果很好,但基本上,所有异步的东西都需要一些时间来执行,在大多数情况下,我想要并行处理块。我会在done的顶部放置_transform,但由于某些原因,这不是解决方案,其中一个原因是当最后一个块将调用done时,下一个push会抛出错误Error: stream.push() after EOF。因此,如果最后一个块已经使用done调用,我们就无法推送。

对于处理这种情况,我将_flush转换的方法与chunk的队列计数器结合使用(当它进入时增加,在push减少)。该死的,已经有这么多的话了,所以这里只是我代码的一个例子。

const minute = 60000;
const second = 1000;

const CRITICAL_QUEUE_POINT = 50;

export class AsyncTransform extends Transform {

    private queue: number = 0;

    constructor() {
        super({objectMode: true});
    }

    public async _transform(chunk, enc, done) {

        this.checkQueue()
            .then(() => this.init(chunk, done));
    }

    public _flush(done) {
        this._done(done, true);
    }

    private async init(chunk, done) {
        this.increaseQueueCounter();
        this._done(done);

        const user = await new UserRepository().search(chunk);

        this.decreaseQueueCounter();
        this._push(user);
    }

    /**
     * Queue
     * */
    private checkQueue(): Promise<any> {
        return new Promise((resolve) => {
            const _checkQueue = () => {
                if (this.queue >= CRITICAL_QUEUE_POINT) {
                    return setTimeout(_checkQueue, second * 10);
                }

                resolve();
            };

            _checkQueue();
        });
    }

    private increaseQueueCounter(): void {
        this.queue++;
    }

    private decreaseQueueCounter(): void {
        this.queue--;
    }

    /**
     * Transform API
     * */
    private _push(user) {
        this.push(user);
    }

    private _done(done, isFlush: boolean = false) {
        if (!isFlush) {
            return done();
        }

        if (this.queue === 0) {
            return done();
        }

        setTimeout(() => this._done(done, isFlush), second * 10);
    }

}

1 个答案:

答案 0 :(得分:1)

两年前我考虑过这个问题并且正在寻找解决方案 - 某种框架。我找到了几个框架 - 高地和事件流 - 但是所有框架都使用起来非常复杂,我决定写一个新框架:scramjet

现在您的代码可以简单:

const {DataStream} = require('scramjet');

yourDataStream.pipe(new DataStream())
    .map(async (chunk) => {
         await checkQueue();
         return new UserRepository().search(chunk);
    });

或者,如果我正确理解checkQueue(),它只会将同时连接数保持在临界水平以下,那么它就更简单了:

yourDataStream.pipe(new DataStream({maxParallel: CRITICAL_QUEUE_POINT }))
    .map(async (chunk) => new UserRepository().search(chunk));

它会将连接数保持在一个稳定的水平(每当有一个响应它会开始一个新线程)。