Deka Nos
Deka Nos

Reputation: 31

How does this asyncHandler function work?

I'm talking about this function:

const asyncHandler = (fn) => (req, res, next) => Promise.resolve(fn(req, res, next)).catch(next);

I am keen to understand step by step what is going on when we pass it this function:

getBootcamps = asyncHandler(async (req, res, next) => {
  const bootcamps = await Bootcamp.find();
  res.status(200).json({ success: true, count: bootcamps.length, data: bootcamps });
});

Upvotes: 2

Views: 1356

Answers (2)

Mulan
Mulan

Reputation: 135197

A combinator is a higher-order function that uses only function application and earlier defined combinators to define a result from its arguments.

So we have a combinator, asyncHandler -

const asyncHandler = (fn) => (req, res, next) => Promise.resolve(fn(req, res, next)).catch(next)

And we have getBootcamps -

const getBootcamps = asyncHandler(...)

So we fill in the definition of asyncHandler -

const getBootcamps = (fn) => (req, res, next) =>
  Promise.resolve(fn(req, res, next)).catch(next)

Where the first argument, fn, is equal to -

async (req, res, next) => {
  const bootcamps = await Bootcamp.find();
  res.status(200).json({ success: true, count: bootcamps.length, data: bootcamps });
}

So that becomes -

const getBootcamps = (req, res, next) =>
  Promise.resolve((async (req, res, next) => {
    const bootcamps = await Bootcamp.find();
    res.status(200).json({ success: true, count: bootcamps.length, data: bootcamps });
  })(req, res, next)).catch(next)

So as asyncHandler's name implies, it accepts a fn which is treated as an async handler. fn can return a promise and asyncHandler will properly wrap it and automatically connect the next callback to properly handle errors. Related: What do multiple arrow functions mean in JavaScript?

This is an effective combinator because it prevents you from having to write this try-catch boilerplate for every async handler you would need -

// without asyncHandler 

async function  getBootcamps (req, res, next) {
  try {
    const bootcamps = await Bootcamp.find()
    res.status(200).json({ success: true, count: bootcamps.length, data: bootcamps })
  }
  catch (err) {
    next(err)
  }
}

Instead asyncHandler allows you to focus on "success" path and automatically handles the "error" path for you -

// with asyncHandler

const getBootcamps = asyncHandler(async (req, res, next) => {
  const bootcamps = await Bootcamp.find()
  res.status(200).json({ success: true, count: bootcamps.length, data: bootcamps })
})

Upvotes: 1

Robert Kawecki
Robert Kawecki

Reputation: 2438

It generates a new function, based on existing function fn, with the signature:

(req, res, next)

This generated function's body is:

return Promise.resolve(fn(req, res, next)).catch(next)

What it does is:

  • call the fn (the underlying handler) with arguments (req, res, next)
  • pass the return value of that fn call to Promise.resolve() - this converts it to a Promise if it wasn't one already
  • register a callback (next) for when the promise rejects

The code that you've posted seems to be an Adapter that converts an async function to work with next(err)-based error handling (most likely for the express framework). Notably, however, it only seems to care about the error case - it is still the async function's responsibility to send the response, thus ending the request processing.

Some other Web frameworks exist that make this step unnecessary - for instance, fastify can just use async functions as handlers directly and simply return the response object instead of doing an explicit .json() call (which does res.end() under the hood).

Upvotes: 2

Related Questions