docs: function nodecat(fileNames, options, callback) {
    if (!callback && typeof options === 'function') {
      callback = options;
      options = null;
    }
  
    if (!callback) {
      return new Promise((resolve, reject) => {
        nodecat(fileNames, options, (err, result) => {
          if (err) { reject(err); } else { resolve(result); }
        });
      });
    }
  
    if (typeof callback !== 'function') {
      throw new TypeError('callback must be a function');
    }
  
    const callerStreamEnded = {};
    const callerStreams = (options && options.fileStreams) || {};
    const errStream = (options && options.errStream) || process.stderr;
    const outStream = (options && options.outStream) || process.stdout;
  
    try {
      if (!fileNames
          || typeof fileNames !== 'object'
          || fileNames.length !== Math.floor(fileNames.length)) {
        throw new TypeError('fileNames must be an Array-like object');
      }
      if (options && typeof options !== 'object') {
        throw new TypeError('options must be an object');
      }
      if (typeof callerStreams !== 'object') {
        throw new TypeError('options.fileStreams must be an object');
      }
      if (typeof outStream.write !== 'function') {
        throw new TypeError('options.outStream must be a stream.Writable');
      }
      if (typeof errStream.write !== 'function') {
        throw new TypeError('options.errStream must be a stream.Writable');
      }
    } catch (err) {
      process.nextTick(() => {
        callback(err);
      });
      return undefined;
    }
  
    // Error which will be returned from nodecat
    let errNodecat = null;
    // Cleanup function for the currently piping input stream
    let inCleanup;
  
    function allDone() {
      outStream.removeListener('error', onOutError);
      callback(errNodecat);
    }
  
    // Note:  src.unpipe is called by stream.Readable internals on dest 'error'
    function onOutError(err) {
      errNodecat = combineErrors(errNodecat, err);
      errStream.write(`nodecat: ${err}\n`);
      if (inCleanup) {
        inCleanup();
      }
      allDone();
    }
    outStream.once('error', onOutError);
  
    let i = 0;
    function catNext() {
      if (i >= fileNames.length) {
        allDone();
        return;
      }
  
      const fileName = fileNames[i];
      i += 1;
      const callerStream = callerStreams[fileName];
      if (callerStream && callerStreamEnded[fileName]) {
        catNext();
        return;
      }
  
      const inStream = callerStream || fs.createReadStream(fileName);
  
      inCleanup = function cleanup() {
        inStream.removeListener('error', onInError);
        inStream.removeListener('end', done);
      };
  
      function done() {
        if (callerStream) {
          callerStreamEnded[fileName] = true;
        }
        inCleanup();
        catNext();
      }
  
      function onInError(err) {
        // Mark error with the name of the file which caused it
        err.fileName = fileName;
        errNodecat = combineErrors(errNodecat, err);
        errStream.write(`nodecat: ${fileName}: ${err.message}\n`);
        // There is no way to know whether more data may be emitted.
        // To be safe, unpipe to prevent interleaving data after starting next.
        if (typeof inStream.unpipe === 'function') {
          inStream.unpipe(outStream);
        }
        done();
      }
      inStream.once('error', onInError);
      inStream.once('end', done);
  
      inStream.pipe(outStream, {end: false});
    }
  
    catNext();
    return undefined;
  }