Oli Oli - 1 year ago 71
Node.js Question

Refactoring node.js functions without losing scope

I'm having trouble refactoring my node.js application.

Consider the following (very hypothetical) example using express, where accessResource is just a mock for an arbitrary function using a callback that is not in my power to change.

var express = require('express');
var app = express();

app.get('/', function(request, response) {
accessResource(request, response, function sendResponse(err, data) {
if (err) {
return response.status(500).send({"message": err});
} else {
return response.status(200).send({"message": data});


I would like to refactor sendResponse into a standalone function, so that I can ultimately move it to its own module.

app.get('/', function(request, res) {
accessResource(request, response, sendResponse);

function sendResponse(err, data) {
if (err) {
return response.status(500).send({"message": err});
} else {
return response.status(200).send({"message": data});

Now obviously this won't work anymore, since response is not available in the scope of sendResponse.

It seems like I would have to wrap sendResponse in another function, but most of all I feel like I am missing a fundamental concept here ...

Answer Source

The piece of the puzzle that will change your life is higher-order functions.

I'm not going to say that they're going to solve all of your life's problems, but once you get used to how they work, problems like this become much easier.

You can, and should, learn about setting up middleware and whatnot, but that's only a solution specifically in the realm of express. Functional Composition is something that is happily used all through JS.

function makeResponseHandler (response) {
  function sendResponse (err, data) {
      return err
        ? response.status(500).send({"message": err})
        : response.status(200).send({"message": data});
  return sendResponse;

In ES6, I might just write this as:

function makeResponseHandler = (response) => (err, data) => err
  ? response.status(500).send({"message": err})
  : response.status(200).send({"message": data});

app.get("./", (request, response) => {
  accessResource(request, response, makeResponseHandler(response));

The moral of the story is that functions can be passed in as values; you already know this, whether or not you know you know it. You've been using callbacks, ergo, you've been passing them as values.

The corollary to this, and what makes JS super-powerful, is that you can also return functions as values.
Any function you create inside will have access to whatever else it sees inside the function. Even when you return it as a value to the outside.

So if I create a callback function inside of a factory, which takes a reference to the response you want to use, then I will always have that response available to me in the callback I created in the factory.

function rememberX (x) {
  return function retrieveX () { return x; };

const get32 = rememberX(32);
const get24 = rememberX(24);
const getTheAnswer = rememberX(42);

const x = 88;
get32(); // 32
get24(); // 24
getTheAnswer(); // 42

Note that they all remember the value of x that the outer function was given when each instance of the function was made. Also notice that none of them care about the external value of x, because they all have an internal x they're referring to.

So you can use an outer function which takes the response you want to work with on that occasion, and returns a function that operates like your regular callback.

This is handy for so many other cases in modern JS development.

const pluck = key => obj => obj[key];
const greaterThanX = x => y => y > x;
const getName = pluck("name");

const people = [{ name: "Bob"  }, { name: "Susan" },
                { name: "Doug" }, { name: "Sarah" }];

people.map(getName) // ["Bob", "Susan", "Doug", "Sarah"]
  .map(pluck("length")) // [3, 5, 4, 5]
  .filter(greaterThanX(3)); // [5, 4, 5]

A little contrived, but it's immediately applicable to setting up advanced filters, where you might have the user's filter criteria available, or the pagination data available, but you don't have the actual data available yet, so you want to build functions that will sort and filter and page, and then just pass the relevant list of data in, and watch your assembly line just do its job.

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download