0

I am trying to understand why the catch block in the upload function does not capture the exception thrown after the request(.....) line in createReleaseVersion function. The nodejs script crashes and the exception is unhandled. Only the error of the exception is shown in the console.

In the following code, I expected to be printed 'got you' then 'after', but it does not happen.

If I replace throw with return reject (... same object) then I get the desired output.
I get first printed 'got you' then 'after'

function createReleaseVersion(releaseVersion) {
    var options = {
        uri: 'https://someurl',
        method: 'POST',
        json: {'version': releaseVersion}
    };

    return new Promise(function(resolve, reject) {
        request(options, function (error, response, body) {
            throw {
                error: error,
                response: response,
                body: body
            };
            console.log(body);
            if (error) {
                throw {
                    error: error,
                    response: response,
                    body: body
                };
            } else {
                resolve();
            }
        });
    });
}

async function upload(releaseVersion) {
    try {
        await createReleaseVersion(releaseVersion);
    } catch (error) {
        console.log('got you');
    }
    console.log('after');
}

upload('ddd');
Kristi Jorgji
  • 1,154
  • 1
  • 14
  • 39
  • Related question and answer: https://stackoverflow.com/questions/25143476/asynchronous-exception-handling-with-bluebird-promises (think it's useful - but not really a duplicate) – Benjamin Gruenbaum Jul 03 '18 at 13:03

1 Answers1

1

Well, let's take a simpler case:

new Promise((resolve, reject) => {
  setTimeout(() => { throw new Error(); });
});

Which crashes the process. The reason is that the setTimeout (or request in your case) subscribes a callback to be called later.

When the callback is eventually called - the promise constructor is done executing already and the error is thrown into the current context (setTimeout/request's).

The reason the promise constructor catches errors synchronously is because it basically does this:

try {
   executor(resolve, reject); // your code
} catch (e) {
   reject(e);
}

Because in your case the function does not execute synchronously - it has no way to know about the exception's context. This might change in the future with Zones but probably will not.

If you want to mark an error inside a promisified function - you can call the second argument (reject) and reject(new Error(...)).

I warmly recommend you use util.promisify instead of converting APIs to promises manually to avoid these sort of issues :)

I also recommend you only reject with Error objects for better stack traces.

Benjamin Gruenbaum
  • 270,886
  • 87
  • 504
  • 504
  • Thank you for the great answer. One more question regarding what you wrote 'if you want to mark an error inside a promisified function - you can call the second argument (reject) and reject(new Error(...))'. Can you please give more details how can I do that approach. You mean to wrap the request call inside resolve ? – Kristi Jorgji Jul 03 '18 at 12:47
  • Sure: `new Promise((resolve, reject) => setTimeout(() => reject(new Error()))` will create a _rejected_ promise - this is instead of `throw new Error` which won't work there. – Benjamin Gruenbaum Jul 03 '18 at 13:03