0

Ideally, functions should do one thing well, and only one thing. Does the same thing apply to what a module should export? Of course, modules aren't always the same thing as functions, but they often are (at least in my experience).

This rule (prefer-default-export) encourages more files that only ever export one thing, as the default, which is better for readability, maintainability, treeshaking, conceptual understanding, etc. -ljharb

This made sense to me for the same reason that functions being short and having a narrow, constrained scope makes sense. Usually, writing a module that just has a single export is easy. But sometimes it seems inevitable that a module somewhere needs to export multiple things. For example, say I want to be able to add a timeout for something, and to be able to clear that timeout (independently). I might do something like this:

// module: timeout-manager.js
import fn from './fn';
let timeoutId;
export const createTimeout = () => {
  // perhaps some non-trivial code goes here
  timeoutId = setTimeout(fn, 60000);
};
export const stopTimeout = () => {
  // perhaps some non-trivial code goes here
  clearTimeout(timeoutId);
};

stopTimeout is always called before createTimeout, so the timeout id can't simply be returned from createTimeout and passed around (which would be cumbersome anyway if the consumers of these two functions are widely separated). This sort of issue isn't limited to timeouts - I think it can occur any time a persistent variable needs to be assigned to in one function and the variable needs to be used in another closely related function (such that the variable's usage should be encapsulated, and not exposed).

Is the fact that I'm exporting more than one thing here troubling? One possibility would be to have separate createTimeout and stopTimeout modules, but then they would both have to import from another module which exports two things, eg

// module: timeout-state.js
let timeoutId;
export const setTimeoutId = (newId) => {
  timeoutId = newId;
};
export const getTimeoutId = () => {
  return timeoutId;
};

Which just kicks the "problem" down the line. Another option is to use something nearly identical to the current code, but to export a single object containing those functions, but that seems like a technical bypass against the spirit of the guideline. (if said guideline is even correct..?)

Is this something I should be worried about, or am I just being silly? Is there a better pattern I might consider?

  • 2
    You could have createTimeout return an object with a stop() method. – immibis May 6 at 2:07
  • The problem with that is that stopTimeout is always called first, and in places far away from where createTimeout is used. so the timeout id can't simply be returned from createTimeout and passed around (which would be cumbersome anyway if the consumers of these two functions are widely separated) – Snow May 6 at 3:17
  • 1
    Why is the caller of stopTimeout stopping a timeout that it didn't start? – immibis May 6 at 3:43
  • Like with a debouncer, when a future action gets scheduled due to some event, but that action may need to be run immediately or be canceled when other (entirely separate) events happen. Or maybe an element or listener needs to be cleared (if it exists) before they get added again. pastebin.com/UsvCB2yb – Snow May 6 at 4:39

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.