Is there a way to wrap an await/async try/catch block to every function?
Asked Answered
U

6

19

So i'm using express.js and looking into using async/await with node 7. Is there a way that I can still catch errors but get rid of the try/catch block? Perhaps a function wrapper? I'm not sure how this would actually execute the function's code and also call next(err).

exports.index = async function(req, res, next) {
  try {
    let user = await User.findOne().exec();

    res.status(200).json(user);
  } catch(err) {
    next(err);
  }
}

Something like this...?

function example() {
   // Implements try/catch block and then handles error.
}

exports.index = async example(req, res, next) {
  let user = await User.findOne().exec();
  res.status(200).json(user);
}

EDIT:

Something more similar to this:

var wrapper = function(f) {
    return function() {
        try {
            f.apply(this, arguments);
        } catch(e) {
            customErrorHandler(e)
        }
    }
}

This would somehow handle the try/catch block but doesn't work:

exports.index = wrapper(async example(req, res, next) {
  let user = await User.findOne().exec();
  res.status(200).json(user);
});

See Is there a way to add try-catch to every function in Javascript? for the non-async example.

Ullyot answered 27/12, 2016 at 17:14 Comment(7)
Just use express-promiseBlubbery
You can't wrap an await without making the wrapping function be async. In this case, why not just use .then() and .catch(). What is await buying you. Error handling needs to be there. You can't just wish it away to some other function.Finicky
I'm looking at ways to remove nested then statements when they may need to fork. The above code is just there as a demonstrative example.Ullyot
@Ullyot - You need to be more specific what you're asking then. There need be no nested .then() handlers in what you have above - you can just chain if there's a need for more than one async operation to be sequenced. You can't hide error handling and trying to is a fool's errand. A big downfall of await is that people tend to just shortchange error handling whereas the need for it is more obvious with .then() and .catch(). You can still do it with await, but it needs `try/catch. Please use real-world code that is as complicated as what you're trying to ask about.Finicky
@Finicky Appreciate the comment. I feel its clear when i ask Is there a way that I can still catch errors but get rid of the try/catch block scaffoldUllyot
FYI, async/await is not part of ES7.Turnstile
There's a great article about this topic with examples here: medium.com/@Abazhenov/…Shirring
B
22

Yes, you can easily write such a wrapper for asynchronous functions as well - just use async/await:

function wrapper(f) {
    return async function() {
//         ^^^^^
        try {
            return await f.apply(this, arguments);
//                 ^^^^^
        } catch(e) {
            customErrorHandler(e)
        }
    }
}

Or you use promises directly, like in this example that is more tailored to express (especially with the number of parameters):

function promiseWrapper(fn) {
    return (req, res, next) => {
         fn(req, res).catch(next);
    };
}
Blubbery answered 27/12, 2016 at 19:11 Comment(4)
This does work. Though, async needs to be applied to the returned function instead of the actual wrapper function. Could you make that edit so that i can mark it is the answer? thanks!Ullyot
Yes, it's an arrow function, but a plain function would've worked as well. What is important is that they are formally declared parameters, i.e. promiseWrapper(…).length == 3, which express uses to distinguish handlers.Blubbery
Can you please explain this for me as I am very new to Express and Javascript and finding it hard to understand how this works. I did not get the fact that we are returning a function which is executing the original function (fn) but how does this execute in practiceCatlaina
@Catlaina "the fact that we are returning a function which is executing the original function" - it seems like you did get it :-) Not sure what else there is to say. You might want to learn about closures in general? Or are you not sure how wrapper is applied?Blubbery
P
2

If someone prefers async/await and Express-specific approach, following snippet could be useful

export function asyncWrap(fn) {
  return async function wrappedFn(req, res, next) {
    try {
      await fn(req, res);
    } catch (err) {
      next(err);
    }
  };
}

it can be used in the router in the following way

customRouter.get('/', asyncWrap(customController.getCustomListHandler));
Prent answered 27/5, 2020 at 21:26 Comment(0)
S
2

So, async functions are actually promises, I came up with this solution:

const asyncWrapper = async promise => {
    try {
        return [null, await promise];
    } catch (err) {
        return [err];
    }
};

const yourAsyncMethod = params => new Promise((resolve, reject) => {
    resolve(params);
});

(async () => {
  // Wrap the executed async method but use the await outside
  const [error, result] = await asyncWrapper(yourAsyncMethod(1));
  
  if (error) {
    // Handle error
  }
  console.log(result);
})();
Synovia answered 5/8, 2020 at 0:11 Comment(0)
E
1

A similar answer here hope can help you

const sthError = () => Promise.reject('sth error');

const test = opts => {
  return (async () => {

    // do sth
    await sthError();
    return 'ok';

  })().catch(err => {
    console.error(err); // error will be catched there 
  });
};

test().then(ret => {
  console.log(ret);
});
Electrolyte answered 12/3, 2018 at 9:15 Comment(0)
B
0
const catchAsyncErrors = (func) => (req, res, next) =>
  Promise.resolve(func(req, res, next)).catch((err) => {
    console.log("error", err);
    next();
  });

export default catchAsyncErrors;

then in controllers:

const currentUserProfile = catchAsyncErrors(async (req, res) => {
  const user = await User.findById(req.user._id);
  res.status(200).json({
    success: true,
    user,
  });
});
Baalman answered 4/7, 2022 at 3:59 Comment(1)
how to do this if i'm using typescript ?Shamble
K
0

I'm using Firebase Functions onRequest method to serve an Express App. The other answers on this page helped me arrive at this solution for catching errors at the top level and avoid having to have a try/catch inside each handler.

The keypoints were

  • add async to the onRequest method
const admin = functions.https.onRequest(async (req, res) => adminApp(req, res));
// adminApp
const app = express();

// middleware here...

addPostHandler(app, '/createUser', createUser);
//  ...other routes

// error middleware here...
  • add await to the handler call
// addPostHandler
export const addPostHandler = (app, route: string, handler) => {
  app.post(route, async (req: express.Request, res: express.Response, next: express.NextFunction) => {
    // try catch here means we don't need to add inside each handler
    try {
      await handler(req, res);
    } catch (error) {
      return next(error);
    }
  });
};
Kajdan answered 18/7, 2022 at 5:55 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.