4

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?

1
  • 1
    It works, right? So it's not "bad"; import syntax can handle circular dependencies. Commented Aug 14, 2020 at 14:41

2 Answers 2

4

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) {
    …
  };
}
Sign up to request clarification or add additional context in comments.

2 Comments

If you don't mind some follow up questions: Does this design pattern have a name? I tried to come up with any solution but couldn't so I am impressed by your solution and would like to read more about when to use this kind of solution. In your own code base, would you prefer to leave the circular dependency as is or would you prefer to use this solution to get rid of it?
I don't think there's a good term for it other than dependency injection in its rawest form, a simple closure. Personally I'd prefer circular dependent modules for function declarations, but there might be other things in the module which could make this more inconvenient. Also the "solution" still has a mutual dependency between inspectRecursively and handleObject that some linters may complain about - the benefit is that it's located in a single file, the disadvantage is that it breaks down if someone calls inspectRecursively before the const is initialised.
-1

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) // => ...

3 Comments

I don't think this answers the question: "Say that I think that the file has grown too large".
The problem is, as Bergi accurately speculates, that the functions have grown too large and in ES6 there is a 1:1 relationship between files and modules
seems a bit of an XY problem. better advice is possible when actual code is pasted. gl!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.