5

I am having an issue with Node.js and module.exports. I understand that module.exports is a call to return an object, that object having whatever properties it is assigned.

If I have a file structure like this:

// formatting.js

function Format(text) {
    this.text = text;
}

module.exports = Format;

with this:

// index.js

var formatting = require('./formatting');

Is there a way to initialize a Format object and use it like this?

formatting('foo');
console.log(formatting.text);

Whenever I try to do it that way, I get an error that says formatting is not a function. I then have to do it like this:

var x = new formatting('foo');
console.log(x.text);

which seems cumbersome.

In modules like keypress and request, they can be used right out of the gate, like this:

var keypress = require('keypress');

keypress(std.in);

or

var request = require('request);

request('http://www.google.com', function (error, response, body) {
  if (!error && response.statusCode == 200) {
    console.log(body) // Show the HTML for the Google homepage.
  }
})

How does this work?

apizzimenti
  • 427
  • 2
  • 7
  • 19

3 Answers3

5

I'd suggest wrapping the new call in it's own function then returning that:

function Format(text) {
  this.text = text;
}

function formatting(text) {
  return new Format(text);
}

module.exports = formatting;

This way you should still be able to do:

var format = formatting('foo');
console.log(format.text);


Edit:

In terms of the request stuff, one thing you have to remember is that in JavaScript, functions are still objects. This means you can still add properties and methods to them. This is what they're doing in request though overall it's a bit too complicated to explain every detail in this answer. From what I can tell, they add a bunch of methods (functions on an object) to the request function. This is why you can immediately call those methods like request(blah, blah).pipe(blah).on(blah) Based on what's returned from calling the request function, you can chain some of the other methods on the back of it. When you're using request it's not an object, it's a function (but still technically an object). To demonstrate how functions are still objects and how it's possible to add methods to them, check out this simple example code:

function hey(){
  return;
}

hey.sayHello = function(name) {
  console.log('Hello ' + name);
} 

hey.sayHello('John'); //=> Hello John

This is basically what they're doing, just a lot more complicated and with a lot more stuff going on.

Sam
  • 1,115
  • 1
  • 8
  • 23
  • Yes, that works, but can the object instead be initialized by calling `formatting('foo')` instead of `var format = formatting('foo')`? – apizzimenti Dec 29 '15 at 05:25
  • 1
    What do you mean? When you initialize it don't you want to set the returned object to a variable? Yes I guess it's possible to do something like `console.log(formatting('foo').text)` but what would be the point of that? – Sam Dec 29 '15 at 05:28
  • Right, I see. In your second code block, `formatting` is a reference to `require('./formatting')`, correct? – apizzimenti Dec 29 '15 at 05:34
  • 1
    Yes, if at the top with all of your other requires you did `var formatting = require('./formatting');`, that's where I got the `formatting()` function from. – Sam Dec 29 '15 at 05:35
  • 1
    Also, if you're curious about how `request` works, you should check out the code that makes it run: https://github.com/request/request/blob/master/index.js – Sam Dec 29 '15 at 05:36
  • The reason I came up with this question is because I'd been browsing the `request` repo for hours and didn't understand how it could return an object and have it immediately able to have functions called on it. I looked through `index.js`, `request.js`, and a few files in `/lib`, and didn't understand how it worked (I couldn't really follow the different references to `module.exports` and `exports` throughout each file). – apizzimenti Dec 29 '15 at 05:42
  • Check out my new edit. I'm sure there's much more that someone can elaborate about but hopefully this helps you understand how `request` works a little better. I wouldn't feel comfortable trying to explain it any further, because I'd be way out of my league. – Sam Dec 29 '15 at 06:15
3
module.exports = Format;

This will return the Format constructor when you will require('./formatting')

On the other hand the code below will return an instance of Format, which you can directly call methods on:

module.exports = new Format();
lleaff
  • 4,249
  • 17
  • 23
  • If I do this and then do `formatting('text')`and I try to console.log the `text` property of the object, it still says that `formatting is not a function`. Am I doing something incorrectly? – apizzimenti Dec 29 '15 at 04:58
  • Yes, because once you create an object out of a Constructor with `new`, it's no longer a function but an object created by applying your *Constructor* function to a new object. So if you want to have a module that is at the same time an object and a function like jQuery for example, the constructor pattern isn't the right way to do it. Instead, first export a function and then attach properties to it. – lleaff Dec 29 '15 at 05:13
  • @lleaff so if I do the second option and then in index.js run `format('foo')`, then it will assign 'foo' to the `text` property? `module.exports` is returning an empty instance of the class, and then calling its constructor function with `format('foo')` correctly assigns the property? – apizzimenti Dec 29 '15 at 05:21
  • 2
    @apizzimenti Checkout Sam's answer I think this is what you mean to do. – lleaff Dec 29 '15 at 05:24
1

Try this:

module formatting:

function Format() {
    this.setter = function(text) {
      this.text = text;
    }
    this.show = function() {
      console.log(this.text);
    } 
}
//this says I want to return empty object of Format type created by Format constructor.

module.exports = new Format();

index.js

var formatting = require('./formatting');
formatting('Welcome');
console.log(formatting.show());
Pardeep Dhingra
  • 3,916
  • 7
  • 30
  • 56