1
\$\begingroup\$

Is there a way to make this code bellow more DRY or is this fine as it is? I am wondering if there could for instance be a way to combine the two functions in to 1, the output type of which would be determined by some typescript logic but I don't see how yet.

Please let me know if you happen to see anything else worth improving...

import { EntityManager, EntityTarget, In } from 'typeorm';


export async function batchEntitiesBy<Entity, T extends keyof Entity>(
  em: EntityManager,
  entityClass: EntityTarget<Entity>,
  by: T,
  variables: readonly Entity[T][]
): Promise<(Entity)[]> {
  const entities = await em.find(entityClass, { [by]: In(variables as Entity[T][]) });

  // reorder the entities to match by
  const entityMap = new Map<Entity[T], Entity>()
  entities.forEach((e) => {
    entityMap.set(e[by], e)
  })
  return variables.map((v) => {
    const out = entityMap.get(v)
    if (!out) {
      throw new Error(`Could not find ${entityClass} with ${String(by)} ${v}`)
    }
    return out
  })
}

export async function batchEntitiesArrayBy<Entity, T extends keyof Entity>(
  em: EntityManager,
  entityClass: EntityTarget<Entity>,
  by: T,
  variables: readonly Entity[T][]
): Promise<Entity[][]> {
  const entities = await em.find(entityClass, { [by]: In(variables as Entity[T][]) });

  // reorder the entities to match by
  const entityMap = new Map<Entity[T], Entity[]>()
  entities.forEach((e) => {
    const el = entityMap.get(e[by]) ?? []
    el.push(e)
    entityMap.set(e[by], el)
  })
  // ? do we need this []?
  return variables.map((v) => {
    const out = entityMap.get(v)
    if (!out) {
      throw new Error(`Could not find ${entityClass} with ${String(by)} ${v}`)
    }
    return out
  })
}
```
\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

Single function

Since both functions have identical input signatures, I don't see a way to merge them into one. You have to somehow tell the program, what kind of return value you want if it's 1d or 2d array of Entities. Either by additional parameter or having 2 functions (which I think is better anyway).

In some languages with strong static typing and generics support you could tell also possibly say this information by passing generics type of your return object, because generic types are parts of the method signature. It would be possible in C# or using kotlin reified inline functions, but not in Javascript or JVM languages as they don't know the generic type on runtime.

DRY

Only difference is in your entityMap variable and it's construction. I suggest this:

  • Extract common code into a 3rd private function
  • Call this function from your 2 public functions
  • Specific code (constructing entityMap) keep in your public functions

Your private function can look something like this:

private async function batchEntitiesBy<Entity, T extends keyof Entity, Result>(
    entityClass: EntityTarget<Entity>,
    by: T,
    variables:  Entity[T][],
    entityMap:  Map<Entity[T], Result>,
): Promise<Result[]> {
       return variables.map((v) => {
        const out = entityMap.get(v)
        if (!out) {
            throw new Error(`Could not find ${entityClass} with ${String(by)} ${v}`)
        }
        return out
    })
}

It may not be syntactically correct as I am not too experienced with Typescript so apologies for any errors, but it should give you the idea. Result generics should be either Entity or Entity[].

Constructing your maps

You can use functional approach to convert your entities to entityMap. In your case you can simplify it to this for the first function: entities.reduce((obj, item) => obj[item[by]] = item, {})

\$\endgroup\$
2
  • \$\begingroup\$ Why do you prefer having 2 functions over having an additional parameter? \$\endgroup\$
    – sev
    Oct 22, 2022 at 10:43
  • 1
    \$\begingroup\$ In this case I find it as more readable. If I was to read this as API documentation, I may miss, that it can return 1d or 2d array and get lost by number of parameters. With 2 functions that wouldn't happen. Also overall it's better to have more smaller functions with less parameters rather than having one big. You want to get away from god object pattern. \$\endgroup\$
    – K.H.
    Oct 22, 2022 at 10:49

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.