How to avoid circular dependency between modules when working with recursive functions?

Viewed 1833

The following code traverses an object and its keys. It does a thing for each key depending on what kind of value it refers to. It works well. All is good.

const obj = {
  prop1: {
    prop2: 1,
    prop3: {
      prop4: 2
    }
  },
  prop5: "1"
}

function inspectRecursively(node) {
  switch (typeof node) {
    case "object":
      handleObject(node);
      break;
    case "string":
      handleString(node);
      break;
    case "number":
      handleNumber(node);
      break;
    default:
      break;
  }
}

function handleObject(node) {
  console.log(JSON.stringify(node, null, 2))
    
  for (const [key] of Object.entries(node)) {
    inspectRecursively(node[key]);
  }
}

function handleString(node) {
  console.log(node)
}

function handleNumber(node) {
  console.log(node.toString().padStart("0", 3))
}

inspectRecursively(obj)

Say that I think that the file has grown too large. I split it up into modules

main.js

import { importRecursively } from "./importRecursively"

const obj = {
  prop1: {
    prop2: 1,
    prop3: {
      prop4: 2
    }
  },
  prop5: "1"
}
 inspectRecursively(obj)

importRecursively.js

import { handleObject } from "./handleObject.js"
import { handleString} from "./handleString.js"
import { handleNumber} from "./handleNumber.js"

function inspectRecursively(node) {
  switch (typeof node) {
    case "object":
      handleObject(node);
      break;
    case "string":
      handleString(node);
      break;
    case "number":
      handleNumber(node);
      break;
    default:
      break;
  }
}

handleObject.js

import { importRecursively } from "./importRecursively"

function handleObject(node) {
  console.log(JSON.stringify(node, null, 2))
    
  for (const [key] of Object.entries(node)) {
    inspectRecursively(node[key]);
  }
}

handleString.js

function handleString(node) {
  console.log(node)
}

handleNumber.js

function handleNumber(node) {
  console.log(node.toString().padStart("0", 3))
}

Now I end up with a circular dependency.

main -> inspectRecursively -> handleObject -> importRecursively

I think this is bad, but am not sure about it?

What do I do in this situation? Do I change something to avoid the circular dependency?

2 Answers

I think this is bad, but am not sure about it?

No, it's not bad. ES6 modules do handle circular dependencies like this totally fine. Just make sure that all of these modules are pure and only contain function declarations, not top-level code that constructs values that depend on imported values: declarations are "hoisted" across modules.

Do I change something to avoid the circular dependency?

You could, by using explicit dependency injection:

// inspectRecursively.js
import { makeHandleObject } from "./handleObject.js"
import { handleString} from "./handleString.js"
import { handleNumber} from "./handleNumber.js"

const handleObject = makeHandleObject(inspectRecursively);

function inspectRecursively(node) {
  …
}
// handleObject.js

export function makeHandleObject(inspectRecursively) {
  return function handleObject(node) {
    …
  };
}

I would write Inspect as its own module -

// Inspect.js

const inspect = v =>
  // ...

const handleString = e =>
  // ...

const handleObject = e =>
  // ...

const handleNumber = e =>
  // ...

export { inspect } // only export functions the user should call

I don't really see the purpose of breaking each handle* into its own file.

Now when you use it in your program -

import { inspect } from './Inspect'

inspect(someObj) // => ...
Related