0

This question concerns some nodejs code. I am trying to clear my mind on how exceptions are handled, and how promise/async functions are alike/unlike.

I have greatly simplified my code, providing a (non-functional) sample below. I have a function (uploadFile) returning a promise. That function calls another async function which throws an exception.

const Linebyline = require('line-by-line');

const uploadFile = (filename) => new Promise((resolve, reject) => {
    console.log(`Upload ${filename}`);
    const lr = new Linebyline(filename);
    //
    // Point A: throw from here works, no unhandled rejection
    //

    lr.on('line', async (line) => {
        // Another async function throws an error here,
        // replacing it with a simple "throw" to clarify things:
        throw new Error("failed");
        // Point B: this throw fails, unhandled rejection
    }

    // Other code continues, calling resolve and reject...
}

async function top() {
    try {
        await uploadFile('test.txt');
    } catch (err) {
        console.log(`Err: ${err}`);
    }
}

With this scheme, I get an unhandled rejection. Since I have a try/catch in the upper level function calling uploadFile, I wax expecting that catch to handle pretty much everything going wring in sub calls.

If I catch the "throw" inside uploadFile and transform it into a reject, then all is fine.

Obviously, not calling reject, I am looking for trouble, but I don't really understand the chain of events generated by that "throw"?

Thank you.


Edit, 01/31/2019

Edited the source code, to outline the core issue. When my function throws from point B (inside the callback), I end up with an unhandled rejection. When throwing from A, no unhandled rejection.

The core of the question is really what happens to that exception thrown from point B.

This code sample doesn't call resolve/reject, but this is done elsewhere in the real code.

Will59
  • 1,430
  • 1
  • 16
  • 37
  • 1
    The `lr.on`callback has *nothing* to do with the rest of the promise chain. – jonrsharpe Dec 30 '19 at 18:57
  • I think your throw statement will throw its own syntax error as I don’t think “throw” is a function – evolutionxbox Dec 30 '19 at 19:03
  • @evolutionxbox no, it's not a function, but the parentheses aren't syntactically invalid (they just "group" the single value). – jonrsharpe Dec 30 '19 at 19:06
  • @jonrsharpe: actually it does, as whether the throw happens in the call back or not makes a difference (nothing to do with the line-by-line module, but simply with the fact I'm using an async callback). – Will59 Dec 31 '19 at 07:58
  • @evolutionxbox : right, I removed those from this pseudo-code, thanks. Like jonrsharpe said, it doesn't hurt, but it doesn't do much either to put these in. – Will59 Dec 31 '19 at 08:00
  • 1
    It doesn't, which is *why the rejection is unhandled*. If it was part of the rest of the promise chain, that rejection would be handled by your catch, but `lr.on` clearly doesn't have the machinery to resolve the promise; the callback is getting invoked but nothing is waiting for the result. – jonrsharpe Dec 31 '19 at 08:02
  • @jonrsharpe: OK, so the core of the problem is that if my function invokes a callback, and an exception happens inside that callback, there is no mechanism that will "bubble" the exception back to the promise chain? Whereas if it happens in the executor's main body, there is? – Will59 Dec 31 '19 at 08:07
  • 1
    It's not your function invoking it, but almost. If an *async* function gets invoked by something that doesn't implement a way to handle the result, it creates a separate promise chain and nothing is there to handle its failure (or success, for that matter; you can't get the line out, either). If you had a non-async callback you wouldn't see an unhandled promise rejection (but you still wouldn't be able to catch that error). – jonrsharpe Dec 31 '19 at 08:10
  • @jonrsharpe: OK, thanks for the info, that's what I was looking for (the creation of that new promise). If you care, you can put this in an answer I will accept. Regarding your last comment: This is strange, as when throwing from point A (which I think would be equivalent to a sync callback?), my 'top' function does properly catch the exception? – Will59 Dec 31 '19 at 08:16
  • I'm not sure point A is equivalent to a sync callback; try actually using a sync callback and see, though. – jonrsharpe Dec 31 '19 at 08:18
  • Actually, scratch that, I'd guess it will be. Still worth the practical experiment, though! – jonrsharpe Dec 31 '19 at 08:22

1 Answers1

1

You're using Promise constructors wrong. You should be calling reject on error or resolve on success. You're doing neither. If you just call reject in your promise, then await will throw the error, which is what you're trying to make happen here.

IceMetalPunk
  • 5,476
  • 3
  • 19
  • 26
  • Thanks for your answer. I edited the nun-functional code to clarify things. My real function calls resolve/reject elsewhere, but since the question is about how this precise exception is handled, and why it is not treated the same when thrown from 'point A' or 'point B', I removed the rest of the code to get to the core issue. – Will59 Dec 31 '19 at 08:03