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 */?

3
  • Maybe this will help Commented Aug 21, 2022 at 17:32
  • 3
    Unless you want to risk invoking it in a try/catch you can't: stackoverflow.com/questions/40922531/… Commented Aug 21, 2022 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. Commented Aug 21, 2022 at 19:33

1 Answer 1

0

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");
}

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

2 Comments

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 } );
How could this help? isConstructor returns false for Error and () => new Error: jsfiddle.net/wp9xsk4h The question is how a class can be distinguished from a function.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.