1

I have a file with the following code:

const logger = (options) => (req, res, next) => {
  if (typeof options === 'object'
      && options !== null
      && options.enable) {
        console.log(
          'Status Code:', res.statusCode,
          'URL:', req.originalUrl,
        )
  }
  next()
}

module.exports = logger

It is used as such in a different .js file:

const express = require('express')
const loggerMiddleware = require('./middleware-logger')
const app = express()

app.use(loggerMiddleware({
        enable: true,
}))

app.listen(
    1337,
    () => console.log('Web Server listening on 1337'),
)

Can anyone explain what is happening with the series of => in the first line? I understand a normal (req,res,next) => {} to mean something like:

"There is an unnamed function with 3 arguments defined by what is in the curly braces.

In this case with the series of arrow operators, what I do not "get" is how the function def can see all the arguments in both (options) and (req,res,next) at the same time.

What is really going on here under the hood? Does this have something to do with promises? And again, how can the one method def see all the arguments at the same time?

marcus
  • 77
  • 1
  • 9
  • Simple answer is it is a function that returns another function – charlietfl Sep 11 '18 at 17:10
  • There are *two* functions. The first one, which will be the value of `logger`, takes one argument: the `options`. The other function is *returned* whenever `logger` is called. – Pointy Sep 11 '18 at 17:10
  • It is called currying. which as @charlietfl and the others in comment all mentioned the functionality. Here is a blog someone wrote on it currying in ES6 in particular. https://blog.benestudio.co/currying-in-javascript-es6-540d2ad09400 – Jacob Sep 11 '18 at 17:16
  • @Jacob it is really similar to currying. But if you talk currying you normally mean to fix one or more arguments of a function that expects mulpible arguments. – t.niese Sep 11 '18 at 17:28

1 Answers1

5

The code you have shown is a shorthand for:

const logger = function(options) {
  return function(req, res, next) {
    if (typeof options === 'object' &&
      options !== null &&
      options.enable) {
      console.log(
        'Status Code:', res.statusCode,
        'URL:', req.originalUrl,
      )
    }
    next()
  }
}

If you call loggerMiddleware({enable: true,}) it will return a new function function(req, res, next) { ... } that creates a closure over the options parameter.

This allows this returned function (in this case a middleware) have access to the passed option object on each request without the need of polluting the global scope.

And as of that also allows to create two middlewares with differnt options:

app.get('/with-logging', loggerMiddleware({
  enable: true,
}), function(res, res) {})


app.get('/without-logging', loggerMiddleware({
  enable: false,
}), function(res, res) {})

Personally I think those (options) => (req, res, next) => { constructs are not really readable and remindes me of those fancy nested ternary conditional operator (?:) cobinations.

t.niese
  • 39,256
  • 9
  • 74
  • 101
  • Well, nested ternaries get pretty wacky after anything past two expression conditions. I use ternaries a lot and only recently the other day used currying for a handleClick() in my React component., was the only way I could think of to initialize the property. – Jacob Sep 11 '18 at 17:26
  • 1
    @Jacob closures and currying is great. What I wanted to say is that `function(option) { return (req, res, next) => { } }` or `function(option) { return function(req, res, next) { } }` is most of the time easier to read then `(options) => (req, res, next) => {`. Arrow functions are often overused, just because they are fancy and the code is shorter. – t.niese Sep 11 '18 at 17:32
  • I cant argue with people getting too caught up in the newest hotness. How it goes too is always make it work first! – Jacob Sep 11 '18 at 17:42