What's the proper way to handle back-pressure in a node.js Transform stream?

前端 未结 6 661
天命终不由人
天命终不由人 2020-12-25 13:55

Intro

These are my first adventures in writing node.js server side. It\'s been fun so far but I\'m having some difficulty understanding the proper way to implement

6条回答
  •  臣服心动
    2020-12-25 14:33

    I ended up following Ledion's example and created a utility Transform class which assists with backpressure. The utility adds an async method named addData, which the implementing Transform can await.

    'use strict';
    
    const { Transform } = require('stream');
    
    /**
     * The BackPressureTransform class adds a utility method addData which
     * allows for pushing data to the Readable, while honoring back-pressure.
     */
    class BackPressureTransform extends Transform {
      constructor(...args) {
        super(...args);
      }
    
      /**
       * Asynchronously add a chunk of data to the output, honoring back-pressure.
       *
       * @param {String} data
       * The chunk of data to add to the output.
       *
       * @returns {Promise}
       * A Promise resolving after the data has been added.
       */
      async addData(data) {
        // if .push() returns false, it means that the readable buffer is full
        // when this occurs, we must wait for the internal readable to emit
        // the 'drain' event, signalling the readable is ready for more data
        if (!this.push(data)) {
          await new Promise((resolve, reject) => {
            const errorHandler = error => {
              this.emit('error', error);
              reject();
            };
            const boundErrorHandler = errorHandler.bind(this);
    
            this._readableState.pipes.on('error', boundErrorHandler);
            this._readableState.pipes.once('drain', () => {
              this._readableState.pipes.removeListener('error', boundErrorHandler);
              resolve();
            });
          });
        }
      }
    }
    
    module.exports = {
      BackPressureTransform
    };
    

    Using this utility class, my Transforms look like this now:

    'use strict';
    
    const { BackPressureTransform } = require('./back-pressure-transform');
    
    /**
     * The Formatter class accepts the transformed row to be added to the output file.
     * The class provides generic support for formatting the result file.
     */
    class Formatter extends BackPressureTransform {
      constructor() {
        super({
          encoding: 'utf8',
          readableObjectMode: false,
          writableObjectMode: true
        });
    
        this.anyObjectsWritten = false;
      }
    
      /**
       * Called when the data pipeline is complete.
       *
       * @param {Function} callback
       * The function which is called when final processing is complete.
       *
       * @returns {Promise}
       * A Promise resolving after the flush completes.
       */
      async _flush(callback) {
        // if any object is added, close the surrounding array
        if (this.anyObjectsWritten) {
          await this.addData('\n]');
        }
    
        callback(null);
      }
    
      /**
       * Given the transformed row from the ETL, format it to the desired layout.
       *
       * @param {Object} sourceRow
       * The transformed row from the ETL.
       *
       * @param {String} encoding
       * Ignored in object mode.
       *
       * @param {Function} callback
       * The callback function which is called when the formatting is complete.
       *
       * @returns {Promise}
       * A Promise resolving after the row is transformed.
       */
      async _transform(sourceRow, encoding, callback) {
        // before the first object is added, surround the data as an array
        // between each object, add a comma separator
        await this.addData(this.anyObjectsWritten ? ',\n' : '[\n');
    
        // update state
        this.anyObjectsWritten = true;
    
        // add the object to the output
        const parsed = JSON.stringify(sourceRow, null, 2).split('\n');
        for (const [index, row] of parsed.entries()) {
          // prepend the row with 2 additional spaces since we're inside a larger array
          await this.addData(`  ${row}`);
    
          // add line breaks except for the last row
          if (index < parsed.length - 1) {
            await this.addData('\n');
          }
        }
    
        callback(null);
      }
    }
    
    module.exports = {
      Formatter
    };
    

提交回复
热议问题