120

in es6 there you can define a module of functions like this

export default {
    foo() { console.log('foo') }, 
    bar() { console.log('bar') },
    baz() { foo(); bar() }
}

the above seems to be valid code, but if I call baz() it throws an error:

ReferenceError: foo is not defined

How do you call foo from another function? in this case baz

Edit

Here's the code that actually doesn't work. I have simplified the code so it's only the core as needed

const tokenManager =  {
  revokeToken(headers) { 
    ... 
  },
  expireToken(headers) {
    ...
  },
  verifyToken(req, res, next) {
    jwt.verify(... => {
      if (err) {
        expireToken(req.headers)
      }
    })
  }
}

export default tokenManager 

and the error is

expireToken(req.headers);
        ^
ReferenceError: expireToken is not defined

Edit 2

I just tried adding tokenManager before expireToken and it finally works

chrs
  • 5,906
  • 10
  • 43
  • 74
  • 1
    See my or @pawel's answer. To fix, replace `expireToken(req.headers)` with `tokenManager.expireToken(req.headers)` or with `this.expireToken(req.headers)`. – skozin Oct 16 '15 at 20:47

3 Answers3

219

The export default {...} construction is just a shortcut for something like this:

const funcs = {
    foo() { console.log('foo') }, 
    bar() { console.log('bar') },
    baz() { foo(); bar() }
}

export default funcs

It must become obvious now that there are no foo, bar or baz functions in the module's scope. But there is an object named funcs (though in reality it has no name) that contains these functions as its properties and which will become the module's default export.

So, to fix your code, re-write it without using the shortcut and refer to foo and bar as properties of funcs:

const funcs = {
    foo() { console.log('foo') }, 
    bar() { console.log('bar') },
    baz() { funcs.foo(); funcs.bar() } // here is the fix
}

export default funcs

Another option is to use this keyword to refer to funcs object without having to declare it explicitly, as @pawel has pointed out.

Yet another option (and the one which I generally prefer) is to declare these functions in the module scope. This allows to refer to them directly:

function foo() { console.log('foo') }
function bar() { console.log('bar') }
function baz() { foo(); bar() }

export default {foo, bar, baz}

And if you want the convenience of default export and ability to import items individually, you can also export all functions individually:

// util.js

export function foo() { console.log('foo') }
export function bar() { console.log('bar') }
export function baz() { foo(); bar() }

export default {foo, bar, baz}

// a.js, using default export

import util from './util'
util.foo()

// b.js, using named exports

import {bar} from './util'
bar()

Or, as @loganfsmyth suggested, you can do without default export and just use import * as util from './util' to get all named exports in one object.

skozin
  • 3,789
  • 2
  • 21
  • 24
  • I tried this, but couldn't make it work. I have edited the question with the real code. Can you see what goes wrong here? – chrs Oct 16 '15 at 20:46
  • 1
    @chrs, see my comment under your question. You need to replace `expireToken` with `tokenManager.expireToken`. – skozin Oct 16 '15 at 20:50
  • @chrs I was faster by a minute ;) But I don't mind, I like the last suggestion here (`export default { foo, bar, baz }`) and probably would use it myself. – pawel Oct 17 '15 at 06:12
  • 2
    "*if you want the convenience of default export and ability to import items individually*" - then you should not re-export an object literal as default, but simply write `import * as util from './util'`. That's precisely what namespace imports were made for. – Bergi Oct 22 '17 at 16:07
31

One alternative is to change up your module. Generally if you are exporting an object with a bunch of functions on it, it's easier to export a bunch of named functions, e.g.

export function foo() { console.log('foo') }, 
export function bar() { console.log('bar') },
export function baz() { foo(); bar() }

In this case you are export all of the functions with names, so you could do

import * as fns from './foo';

to get an object with properties for each function instead of the import you'd use for your first example:

import fns from './foo';
loganfsmyth
  • 156,129
  • 30
  • 331
  • 251
  • 3
    This definitively is the superior solution. Use named exports instead of default objects. – Bergi Oct 22 '15 at 22:32
16

tl;dr: baz() { this.foo(); this.bar() }

In ES2015 this construct:

var obj = {
    foo() { console.log('foo') }
}

is equal to this ES5 code:

var obj = {
    foo : function foo() { console.log('foo') }
}

exports.default = {} is like creating an object, your default export translates to ES5 code like this:

exports['default'] = {
    foo: function foo() {
        console.log('foo');
    },
    bar: function bar() {
        console.log('bar');
    },
    baz: function baz() {
        foo();bar();
    }
};

now it's kind of obvious (I hope) that baz tries to call foo and bar defined somewhere in the outer scope, which are undefined. But this.foo and this.bar will resolve to the keys defined in exports['default'] object. So the default export referencing its own methods shold look like this:

export default {
    foo() { console.log('foo') }, 
    bar() { console.log('bar') },
    baz() { this.foo(); this.bar() }
}

See babel repl transpiled code.

pawel
  • 35,827
  • 7
  • 56
  • 53
  • 2
    When I do it like in your last example I still get `TypeError: Cannot read property 'foo' of undefined` - what am I missing? – leonheess Sep 16 '20 at 10:35