1

To describe the possibillity that function can throw an exception we can use JSDock

/**
 * @throws {Error}
 */
export const a = () => {
  throw new Error("i am exist only to throw exceptions...")
}

Ok, thats cute. But developer could miss that description and do not wrap the function in try...catch.

How can i force typescript / IDE / ES-lint to tell me that i forgot to wrap a function in try...catch?

Should i consider an idea to make an ES-lint plugin? Or is there any ready solution? (except auto-tests with all possible arguments)

Thanks!

  • You may *not want* to wrap something in `try..catch`, if you want the next higher caller to do it instead. It'd be counter productive to *force* you do use `try..catch` all the time. It would be nice if *something* in the call hierarchy would catch the exception, but the dynamic nature of Javascript makes it pretty much impossible to ensure this is happening with a static analyser. – deceze Jan 16 '20 at 11:50
  • @deceze, This is true, but in that case you can forward that throw with another JSDoc annotation (or somehow else). Closest analogy is 'throws ..' in JAVA. This concept is just an idea, and everything may vary. I just interests is there any similar approachecs at all. – Andrey Ponomarenko Jan 16 '20 at 12:42
  • 1
    I think this is a good answer to your question: https://stackoverflow.com/a/49435648/8575545 – mlntdrv Jan 16 '20 at 12:46

1 Answers1

1

try...catch is situational - if you want just to document an exception in JSDoc and/or are not able to handle it immediately yourself (e.g. critical error), you get in trouble with the current approach.

Also, an error is ideally self-documentary and enforces a certain behavior in the code itself - everything else is not as DRY. Code/comments can get out of sync, you might as well forget to document @throws {Error}.

So I am not sure if an IDE or ES-lint plugin is a good idea. Instead you can create some kind of Either data type, that enforces all consumers of a possibly throwing function (a) to react to the error at sometime - otherwise they don't get the return value back. A simplified example:

// wrap a riscy throwing function, so that it returns a plain value ("purify it")
const tryCatch = (myRiskyFn) => {
  try {
    return { val: myRiskyFn() };
  } catch (e) {
    return { err: e };
  }
}

// enforce client to handle error branch here, so we can return a well defined value back.
const valueFromEither = (either, onError) =>
  "err" in either ? onError(either.err) : either.val;

// Test error case:
const a = () => { throw new Error("some error..."); };
const either1 = tryCatch(a); // throws internally
const result1 = valueFromEither(either1, err => `Oh my! ${err}`);
console.log(result1); // Oh my! Error: some error...

// Test success case:
const b = () => 42;
const either2 = tryCatch(b); // goes thru
const result2 = valueFromEither(either2, err => `Oh my! ${err}`);
console.log(`yay! ${result2}`); // yay! 42

(in the style of IO, Either, TaskEither in functional programming approaches, but you can just use it on your own!)

ford04
  • 66,267
  • 20
  • 199
  • 171