1

Is there any alternative to Bluebird's Promise.try function? As I'm using async/await not interested in adding bluebird dependency.

Is there a better way to capture asynchronous error in Node.JS

await Promise.try(async function() {
    // Some code with async operations like readline
    await this.nextCall(batchData, true);
}).catch(async function(err) {
    // My error handling
});

Any inbuilt functions in Node.js 10.x ?

UPDATE :

Is there a better way to capture asynchronous error in Node.JS

try {
    let interfaceVal = lineReader.createInterface({
        input: fs.createReadStream(filePath)
    });
    interfaceVal.on('line', async (line) => {
        throw new Error('My custom Error');
    });
    // some sync operations
} catch(e) {
    // This catch won't get called with custom error
    console.log(e);
}

Any idea to capture such asynchronous errors?

Yilmaz
  • 35,338
  • 10
  • 157
  • 202
Viswanath Lekshmanan
  • 9,945
  • 1
  • 40
  • 64

5 Answers5

2

In nodeJs, if you're using async/await you can use plain try catch blocks and it will catch your errors (and it will work for async calls)

try {
   await this.nextCall(batchData, true);
}
catch(exc) {
 // handle error
}

this function definition needs to be declared as async

Gonzalo.-
  • 12,512
  • 5
  • 50
  • 82
  • Thanks for the answer. This is not about this async/await. Its about asynchronous error. It wont work this way – Viswanath Lekshmanan Oct 16 '18 at 14:55
  • @ViswanathLekshmanan Actually, it will. Considering that this happens inside `async` function (`await` indicates that), `try..catch` catches rejections. I mentioned this in my answer as well. – Estus Flask Oct 16 '18 at 15:06
  • Assume that there is another flow of execution inside your `try catch`, like a callback. `interfaceVal.on('line', async (line) => {});` This wont be handled in this `catch` because the scope is already gone. – Viswanath Lekshmanan Oct 16 '18 at 15:09
  • 1
    @ViswanathLekshmanan This is XY problem. `async` shouldn't be generally used where regular callbacks are expected (i.e. returned promise is ignored) because this results in improper control flow. This is solved with promisification (util.promisify) if the callback is expected to be called once. This won't work with `line` because it's called multiple times. A good way to solve this would be to convert `interfaceVal` to async generator, e.g. with https://github.com/sindresorhus/p-event – Estus Flask Oct 16 '18 at 15:15
  • @Gonzalo Thanks for the info. I dont want to use external package for this. That's why i posted the question :) – Viswanath Lekshmanan Oct 16 '18 at 15:21
  • this is native, no external package is required – Gonzalo.- Oct 16 '18 at 15:22
  • @ViswanathLekshmanan I suppose you confused nicknames. The code in the question doesn't reflect actual problem, otherwise answers would be different. Third-party libs exist to not do same work twice. Node ecosystem heavily relies on the use of NPM libs. It's possible to to write promise wrapper over readline that will do this the right way in about 20 lines, but why? p-event does that. Blubird's `try` isn't needed here. `p-event` is, in my opinion. Otherwise you won't be able to handle errors efficiently, it won't be possible for stream error to abort it. – Estus Flask Oct 16 '18 at 15:46
2

There is no reason to wrap async function with Promise.try. The purpose of Promise.try is to handle both synchronous errors and rejections similarly:

Start the chain of promises with Promise.try. Any synchronous exceptions will be turned into rejections on the returned promise.

This is already done with async since it always returns a promise.

This can be used at top level with async IIFE:

(async function() {
    await this.nextCall(batchData, true);
})().catch(console.error);

Or in case async is nested it can be omitted, a rejection can be handled in parent async function with try..catch, as another answer explains.


In this case an error can be caught only inside async function:

    interfaceVal.on('line', async (line) => {
      try {
        throw new Error('My custom Error');
      } catch (err) {
        console.error(err);
      }
    });

The use of non-promise API (Node stream) doesn't allow for error handling with promises. Stream callback ignores rejected promises and doesn't allow to propagate the error outside the stream.

Callbacks can be converted to promises only when they are expected to be called once. This is not the case with line. In this case asynchronous iterator can be used, this one of its use cases.

Event emitter can be converted to async iterator with p-event and iterated with for await of inside async function:

try {
    let interfaceVal = lineReader.createInterface({
        input: fs.createReadStream(filePath)
    });

    const asyncIterator = pEvent.iterator(interfaceVal, 'line', {
      resolutionEvents: ['close']
    });

    for await (const event of asyncIterator) {
        console.log('line', event);
        // throw new Error('My custom Error');
    }
} catch(e) {
    console.log(e);
}
Estus Flask
  • 206,104
  • 70
  • 425
  • 565
  • I really want to upvote this since it's correct - but I object to that `.catch(console.error)` since promise rejections are already logged :) Mind fixing that? – Benjamin Gruenbaum Oct 16 '18 at 14:41
  • But they aren't logged in this snippet. What exactly do you mean? UnhandledPromiseRejectionWarning? – Estus Flask Oct 16 '18 at 14:45
  • 1
    Unhandled promise rejections are deprecated and will crash Node.js in the future, just like uncaught exceptions. They are logged, indeed, but to properly handle an exception inside an IIFE to avoid the process from crashing you really have to use the `.catch()` here, as shown. – Robert Rossmann Oct 16 '18 at 14:53
  • @estus its not working. It was bind issue previously getting caught. Actual error is not getting caught in this... – Viswanath Lekshmanan Oct 17 '18 at 05:44
  • @ViswanathLekshmanan It will catch *rejections* but not necessarily any asynchronous errors that happen inside. Please, update the question with https://stackoverflow.com/help/mcve that can replicate the problem. – Estus Flask Oct 17 '18 at 06:07
  • @RobertRossmann honestly I'm not sure if we'll actually end up doing that to be fair. Even if we will - the above snippet _should_ likely crash the process on an unhandled promise rejection (like the error handler). You can also just change it to `.catch(err => { /* handle error */ })` which is also fine. – Benjamin Gruenbaum Oct 17 '18 at 07:42
  • @ViswanathLekshmanan Thanks. I updated the answer and added original question to the OP because existing answers don't make sense without it. Initial answers answered your initial question precisely. In future, consider asking a new question in case answers didn't work for you because you asked the wrong question. As I mentioned, this is the use case for `p-event`. You won't be able to handle errors properly with promises and `async` otherwise. – Estus Flask Oct 17 '18 at 10:48
2

Promise.try is on its way for specification. Here is the polyfill:

if (typeof Promise !== 'function') {
    throw new TypeError('A global Promise is required');
}

if (typeof Promise.try !== 'function') {
    Promise.try = {
        try(func) {
            if (typeof this !== 'function') {
                throw new TypeError('Receiver must be a constructor');
            }
            return new this(function (resolve) {
                resolve(func());
            });
        }
    }.try;
}

This polyfill supports promise subclassing and other features. This is safe to polyfill in Node.js.

Benjamin Gruenbaum
  • 270,886
  • 87
  • 504
  • 504
  • 1
    This is in addition to the existing (and correct) answer about not needing to wrap `Promise.try`. – Benjamin Gruenbaum Oct 16 '18 at 14:41
  • I have approved the other answer since it doesn't need any other piece of code. Thanks for pointing this as well. – Viswanath Lekshmanan Oct 16 '18 at 15:00
  • 1
    Upvoted. It's also should be noted that modifying built-ins for private needs is not recommended practice. [Ponyfills](https://github.com/sindresorhus/ponyfill) is the way in the age of modular JS. The dev does a good job at popularizing the concept by providing them for most demanded promise operations as well, including https://www.npmjs.com/package/p-try – Estus Flask Oct 16 '18 at 15:04
  • @ViswanathLekshmanan when you say "this also doesn't work" it doesn't help me diagnose the problem you have and as feedback it doesn't let me improve my answer and help you. Please consider providing me with feedback that will let me address your problem and let me help you :) – Benjamin Gruenbaum Oct 17 '18 at 07:40
0

You don't need to specify a catch clause explicitely in an async function, because it uses the natural Error semantics. Here your function operation_A throws an error, because operation_B rejected.

const operation_B = () => {
    return new Promise((resolve,reject) => {
        window.setTimeout(() => {
            //resolve("RESOLVE: all your base are belong to us");
            reject("REJECT: all your base are belong to us");
        },2000);
    });
};

const operation_A = async () => {
   const B = await operation_B();
   return B.toUpperCase();
};

operation_A().then(console.log).catch(console.error);
code_monk
  • 9,451
  • 2
  • 42
  • 41
0

If you do not want any third-party package you can handle errors on your own.

First write a middleware:

// you could add more logic and create a middleware function
// this should be registered after route handlers
app.use((error, req, res, next) => {
  console.log("error",error)
  console.log('Path: ', req.path)
  next() 
})

write a wrapper function:

const catchAsyncErrors = (func) => (req, res, next) =>
  // passing a controller function for func
  Promise.resolve(func(req, res, next)).catch((err) => {
    // If resolving results in error, our error handler will catch it
    next();
  });

export default catchAsyncErrors;

Wrap your controllers with catchAsyncError

const registerUser = catchAsyncErrors(async (req, res) => {
      .....
      }
Yilmaz
  • 35,338
  • 10
  • 157
  • 202