-1

ok i saw this example of reading a stream and returning a promise using new Promise.

function readStream(stream, encoding = "utf8") {
    
    stream.setEncoding(encoding);

    return new Promise((resolve, reject) => {
        let data = "";
        
        stream.on("data", chunk => data += chunk);
        stream.on("end", () => resolve(data));
        stream.on("error", error => reject(error));
    });
}

const text = await readStream(process.stdin);

My question is why "new Promise" ? can i do it in the 2nd version like

function readStream(stream, encoding = "utf8") {
    
    stream.setEncoding(encoding);

        let data = "";
        
        stream.on("data", chunk => data += chunk);
        stream.on("end", () => Promise.resolve(data));
        stream.on("error", error => Promise.reject(error));

}

const text = await readStream(process.stdin);

Haven't tried it yet, but basically want to avoid the new keyword.


some updates on the 2nd version, since async functions always return a Promise.

A function/method will return a Promise under the following circumstances:

You explicitly created and returned a Promise from it's body. You returned a Promise that exists outside the method. You marked it as async.

const readStream = async (stream, encoding = "utf8") => {
    
    stream.setEncoding(encoding);

        let data = "";
        
        stream.on("data", chunk => data += chunk);
        stream.on("end", () => Promise.resolve(data));
        stream.on("error", error => Promise.reject(error));
}

const text = await readStream(process.stdin);

How's this 3rd version ?

user3552178
  • 2,719
  • 8
  • 40
  • 67
  • 1
    No, second version can't work, because `readStream` no longer returns a promise. The `Promise.resolve` values are going nowhere. – trincot Aug 04 '22 at 19:36
  • 1
    Out of curiosity.. what's wrong with using the *new*-keyword? :) – eol Aug 04 '22 at 19:38
  • @eol well i want to stay with functional style, don't want to use class/new if possible – user3552178 Aug 04 '22 at 20:13
  • Maybe this is interesting for you then: https://stackoverflow.com/a/48516338/3761628 – eol Aug 04 '22 at 20:19
  • Things like error extensions and promises are not related to oop even if you have to use class/new. It is a way to have more readable code – Aziz Hakberdiev Aug 04 '22 at 22:36

2 Answers2

4

If you want readStream to return a promise, you'll have to ... return a promise for readStream (returning a promise in some callback is not doing that).

What the first code is doing, is promisifying the stream API. And that's exactly how it should be done.

The second version of the code is based on a misunderstanding: it seems to hope that returning a promise in the callback passed to the stream.on method, will somehow make readStream return that promise. But when the on callback is called, readStream has already returned. Since readStream has no return statement, it already returned undefined and not a promise.

As a side note, when the stream API calls the callback you passed to the on method, it does not even look at the returned value -- that is ignored.

The third version is an async function, so it now is guaranteed the function will return a promise. But as the function still does not execute a return statement, that promise is immediately resolved with value undefined. Again, the returned values in the callbacks are unrelated to the promise that the async function has already returned.

new keyword

If you want to avoid the new keyword, then realise that anything that can be done with promises can also be done without them. In the end promises are "only" a convenience.

For instance, you could do:

function readStream(stream, success, failure, encoding="utf8") {
    let data = "";
    stream.setEncoding(encoding);
    stream.on("data", chunk => data += chunk);
    stream.on("end", () => success(data));
    stream.on("error", failure);
}

function processText(text) {
    // ... do something with text 
}

function errorHandler(error) {
    // ... do something with the error
}

readStream(process.stdin, processText, errorHandler);

In typical Node style you would pass one callback, for both purposes, as last argument:

function readStream(stream, encoding="utf8", callback) {
    let data = "";
    stream.setEncoding(encoding);
    stream.on("data", chunk => data += chunk);
    stream.on("end", () => callback?.(null, data));
    stream.on("error", err => callback?.(err, null));
}

function processText(err, text) {
    if (err) {
        // do something with err
        return;
    }
    // ... do something with text 
}

readStream(process.stdin, "utf8", processText);

And then you could use the util package to turn that into a promise-returning function:

const util = require('util');

const readStream = util.promisify(function (stream, encoding="utf8", callback) {
    let data = "";
    stream.setEncoding(encoding);
    stream.on("data", chunk => data += chunk);
    stream.on("end", () => callback?.(null, data));
    stream.on("error", err => callback?.(err, null));
});

(async () => {

    try {
        const text = await readStream(stream, "utf8");
        // do something with text
    } catch(err) {
        // do something with err
    }

})();

Of course, under the hood the promisfy function performs new Promise and we're back to where we started.

trincot
  • 317,000
  • 35
  • 244
  • 286
2

You need to construct and return a Promise so that the consumer of the function has something to hook into the asynchronous action being performed. (Another option would be to define the function to also take a callback as an argument.)

If you try to do it the way you're doing with the second snippet, readStream will not return anything, so await readStream(process.stdin); will resolve immediately, and it'll resolve to undefined.

Doing

stream.on("end", () => Promise.resolve(data));

and

stream.on("error", error => Promise.reject(error));

constructs new Promises at that point in the code, but you need the consumer of the function to have access to the Promise that resolves (or rejects) - and so you must have return new Promise at the top level of the function.

CertainPerformance
  • 356,069
  • 52
  • 309
  • 320
  • see my 3rd version – user3552178 Aug 04 '22 at 20:23
  • 1
    It's not any different, making a function `async` doesn't mean that any Promises created inside it are connected with the Promise the function returns. (A function can have multiple promises inside it, after all.) You need a `return`. – CertainPerformance Aug 04 '22 at 20:27
  • just need to add "return" keyword before Promise.resolve(data), Promise.reject(error), right ? – user3552178 Aug 04 '22 at 20:32
  • No, you're already returning them to the caller of the `.on` callback with the arrow functions' implicit return - but that doesn't do you any good because they need to be returned from `readStream`, which is a higher level up. You need to return from the top level of `readStream` for `readStream` to return a value. – CertainPerformance Aug 04 '22 at 20:34