1

I'm completely stumped on how to avoid this circular dependency. I have a TS module that sends emails, and one that handles errors. The error handler writes to a DB and sends emails. And the emailer needs to be able to handle errors. Then most apps use both of them.

For example, something like:

emailer.js

import err from "error-handler.js"

function sendEmail() {
  try { trySendEmail() } 
  catch(e) { err(e) }
}

error-handler.js

import sendEmail from "emailer.js"

function err(e) {
  sendEmail("Error Occurred", e)
}

Is there a right way to handle this situation? Thanks for your help!

4
  • 1
    That's because your idea creates a weird feedback loop: if you fail to send an email, you'll want to send another email about the failure of the first, but it'll also fail, so you'll want to send an email about the failure of sending the email about failure of sending the... Commented Oct 18, 2021 at 14:31
  • @FZs I see what you're talking about, but this has to be a very common situation right? I was planning to add a parameter on the emailer emailError to break out of the loop you're talking about from the error handler. But I'd still like to have the error handling built into the emailer in case an app is using it. And I'd still like to have an emailer that handles errors without needing a wrapper if it's possible. Maybe I need a third module to wrap one of the first two, but that seems excessive. Commented Oct 18, 2021 at 14:44
  • 1
    You can't really resolve this without altering your module structure, and creating at least one additional module. Here's a (relatively simple) feasible solution: module internalemailer sends emails and throws errors, module errorhandler imports internalemailer to send emails and ignores thrown exceptions, and module emailer that sends emails with internalemailer and handles thrown exceptions with errorhandler. Commented Oct 18, 2021 at 15:18
  • 1
    @FZs That sounds good I'll do that, thanks for taking the time to explain it to me! It's unfortunate that I'll need to write hooks to connect the internalemailer with the emailer's error handler, and create a second versioned repo (and a third one for the db module as well), map the rsa keys and everything else, literally just to avoid the circular dependency. Sounds like it's the best/only way though. Commented Oct 18, 2021 at 15:59

1 Answer 1

3

a) there's absolutely no reason not to use a circular dependency here - the two modules do depend on each other, and the code you've written works as-is with ES6 modules, no problems at all. It's no different from putting both function declarations in the same file.

b) break the dependency chain and use dependency injection instead. Either have

// emailer.js
function sendEmail(text, handleError) {
  try { trySendEmail(text) } 
  catch(e) { handleError(e) }
}
// error-handler.js
import sendEmail from "emailer.js"

function err(e) {
  sendEmail("Error Occurred: "+e.message, err)
}

or

// emailer.js
import err from "error-handler.js"

function sendEmail(text) {
  try { trySendEmail(text) } 
  catch(e) { err(e, sendEmail) }
}
// error-handler.js
function err(e, sendEmail) {
  sendEmail("Error Occurred: "+e.message)
}

If you still need to use both in your project, without injecting a dependency in either, you'll need a third module that depends on both and does export a function with the dependency injected.

Sign up to request clarification or add additional context in comments.

4 Comments

This is great thanks @Bergi I'll try the injection. Because they're separate modules I'm guessing I can't have: emailer package.json: dependencies: { "error-handler": 1.0.0 } and error-handler package.json: dependencies: {"emailer": 1.0.0 } without having problems, regardless of ES6. Just disappointed that I have to create a new repo and npm package for a third module that's just a wrapper, feels like a huge over-kill to avoid the circular dependency.
@Sean If they are installed next to each other, that should actually still work. npm appears to have (or have had) issues with installing though, while yarn claims to support this. But really something is wrong with your architecture if you have multiple packages that depend so tightly upon each other. Is the example in the OP your actual use case?
thanks for the info! Yeah a bunch of apps that need a centralized place for emailing, db queries, and error handling. And the db, emailer, and errorhandler are all using each other, which means 2 more packages to wrap the emailer and db mods, which each have to install and repackage when there's a change. It makes sense that npm packs can't point at each other, but so much extra maintenance just to avoid the circular reference. Like you said it feels like the design/architecture is wrong, but I can't figure out where, especially with such a simple example.
Just don't make 3 separate packages for your framework that is used by the apps?

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.