Using promises with streams in node.js

The other solution can look like this:

const streamAsPromise = (readable) => {
  const result = []
  const w = new Writable({
    write(chunk, encoding, callback) {·
      result.push(chunk)
      callback()
    }
  })
  readable.pipe(w)
  return new Promise((resolve, reject) => {
    w.on('finish', resolve)
    w.on('error', reject)
  }).then(() => result.join(''))
}

and you can use it like:

streamAsPromise(fs.createReadStream('secrets')).then(() => console.log(res))

In this line

stream.on("end", resolve(stream.dests[0].path));

you are executing resolve immediately, and the result of calling resolve (which will be undefined, because that's what resolve returns) is used as the argument to stream.on - not what you want at all, right.

.on's second argument needs to be a function, rather than the result of calling a function

Therefore, the code needs to be

stream.on("end", () => resolve(stream.dests[0].path));

or, if you're old school:

stream.on("end", function () { resolve(stream.dests[0].path); });

another old school way would be something like

stream.on("end", resolve.bind(null, stream.dests[0].path));

No, don't do that :p see comments


In the latest nodejs, specifically, stream v3, you could do this:

const finished = util.promisify(stream.finished);

const rs = fs.createReadStream('archive.tar');

async function run() {
  await finished(rs);
  console.log('Stream is done reading.');
}

run().catch(console.error);
rs.resume(); // Drain the stream.

https://nodejs.org/api/stream.html#stream_event_finish


After a bunch of tries I found a solution which works fine all the time. See JSDoc comments for more info.

/**
 * Streams input to output and resolves only after stream has successfully ended.
 * Closes the output stream in success and error cases.
 * @param input {stream.Readable} Read from
 * @param output {stream.Writable} Write to
 * @return Promise Resolves only after the output stream is "end"ed or "finish"ed.
 */
function promisifiedPipe(input, output) {
    let ended = false;
    function end() {
        if (!ended) {
            ended = true;
            output.close && output.close();
            input.close && input.close();
            return true;
        }
    }

    return new Promise((resolve, reject) => {
        input.pipe(output);
        input.on('error', errorEnding);

        function niceEnding() {
            if (end()) resolve();
        }

        function errorEnding(error) {
            if (end()) reject(error);
        }

        output.on('finish', niceEnding);
        output.on('end', niceEnding);
        output.on('error', errorEnding);
    });
};

Usage example:

function downloadFile(req, res, next) {
  promisifiedPipe(fs.createReadStream(req.params.file), res).catch(next);
}

Update. I've published the above function as a Node module: http://npm.im/promisified-pipe