2

In TypeScript, I'm writing a function that takes an Error factory as an argument: either a class name or a factory function. Something like the below:


// Alias from class-transformer package
type ClassConstructor<T> = {
  new (...args: any[]): T;
};

function doSomething(value: number, errorFactory: () => Error | ClassConstructor<Error>) {
  if (value === 0) {
    // Zero is not allowed
    if (/* errorFactory is a class constructor */) {
      throw new errorFactory()
    } else {
      throw errorFactory()
    }
  }
}

In the above function, errorFactory can be an Error class, such that the following code works:

doSomething(0, Error);

Or it can be a function that creates an Error:

doSomething(0, () => new Error());

The issue is that ClassConstructor is a TypeScript type, so it doesn't survive compilation to JavaScript.

typeof(Error) is function, and so is typeof(()=>{}).

So how to determine the parameter's type? What should be the test in /* errorFactory is a class constructor */?

noamtm
  • 12,435
  • 15
  • 71
  • 107
  • Maybe [this](https://www.typescriptlang.org/docs/handbook/2/functions.html#construct-signatures) will help – niceman Aug 21 '22 at 17:32
  • 3
    Unless you want to risk invoking it in a `try/catch` you can't: https://stackoverflow.com/questions/40922531/how-to-check-if-a-javascript-function-is-a-constructor – Dai Aug 21 '22 at 17:49
  • 1
    Please either add a `class-transformer` tag to the question or try to remove the question's apparent dependency on it. If it's a simple type alias for `(new (...args: any) => Error)` then you can use it and not mention `ClassConstructor` at all. – jcalz Aug 21 '22 at 19:33

1 Answers1

-1

By the above code, I understood you need to invoke the constructor with new, and the function without new.

In the above example, Error actually can be invoked without new, it can be called just by typing:

throw Error(msg); // msg: string = The error message

The code below can be used for testing if the function (or constructor) must be called with new:

// Returns:
// - True if the 'func' must be called with 'new'
// - False if the 'func' can be called without 'new'
function isConstructor(func) {
  try {
    // Invoke without 'new'
    func();
  }
  catch (err) {
    if (/^TypeError:.*?constructor/.test(err.toString())) {
      // The error is about that it isn't a normal function
      return true;
    }
    else {
      // The error isn't about that; let's throw it
      throw new err.constructor(err.toString().substr(err.toString().indexOf(" ") + 1), {cause: err});
    }
  }
  return false;
}

// Let's run some tests
console.log(isConstructor("abc".constructor)); // False
// String() returns empty string; it's not a constructor
console.log(isConstructor(() => {}));          // False
// Arrow function
console.log(isConstructor(class {}));          // True
// This *is* a cliss
console.log(isConstructor(Image));             // True
// Some built-in cannot be invoked without 'new'
console.log(isConstructor(throwErr));          // TypeError: Another error
function throwErr() {
  throw new TypeError("Another error");
}
Plantt
  • 230
  • 1
  • 10
  • 1
    `throw err;` <-- Doing this will cause information-loss in the thrown `Error` object (e.g. resetting the stack-trace and other properties) - instead of doing `throw err;` we should throw a new Error and store `err` in that new Error as `cause`: `throw new Error( "human-readable message goes here", { cause: err } );` – Dai Aug 21 '22 at 17:52
  • How could this help? `isConstructor` returns `false` for `Error` and `() => new Error`: https://jsfiddle.net/wp9xsk4h/ The question is how a class can be distinguished from a function. – jabaa Aug 21 '22 at 18:19