Trouble catching a specific JS Error

I’m working with an API that has some objectively poor DDOS Errors.

When the service is denied, it returns a 503 with this body:

<html>
<head><title>503 Service Temporarily Unavailable</title></head>
<body bgcolor="white">
<center><h1>503 Service Temporarily Unavailable</h1></center>
<hr><center>nginx/1.14.0 (Ubuntu)</center>
</body>
</html>
<!-- a padding to disable MSIE and Chrome friendly error page -->
<!-- a padding to disable MSIE and Chrome friendly error page -->
<!-- a padding to disable MSIE and Chrome friendly error page -->
<!-- a padding to disable MSIE and Chrome friendly error page -->
<!-- a padding to disable MSIE and Chrome friendly error page -->
<!-- a padding to disable MSIE and Chrome friendly error page -->

This results in an error that looks like this from rescript side

DAOSquare : 678414857510453309:  {
RE_EXN_ID: 'Promise.JsError/2',
_1: FetchError: invalid json response body at https://app.brightid.org/node/v5/verifications/Discord/cd9b9f98-3190-517a-8d3d-d6c76f916cbc?timestamp=seconds reason: Unexpected token < in JSON at position 0
at /app/node_modules/node-fetch/lib/index.js:273:32
at processTicksAndRejections (node:internal/process/task_queues:96:5)
at async onGuildMemberUpdate (file:///app/apps/bot/src/Bot.mjs:549:19) {
type: 'invalid-json'
}
}

How can I catch this specific JSError? It would be nice to utilize FetchError from node-fetch if possible. Here is how I am logging the error.

catch {
| Exn.Error(obj) =>
  switch Exn.message(obj) {
    | Some(m) => Console.error2(`${guildName} : ${guildId}: `, m)
    | None => ()
   }
| _ => Console.error2(`${guildName} : ${guildId}: `, e)
}
1 Like

I had a look at the node-fetch error docs and it looks like you can switch on the name. I copied the error code from node-fetch into a js file for an example, just to test it out.

(ThrowIt.js)

class FetchBaseError extends Error {
  constructor(message, type) {
    super(message);
    Error.captureStackTrace(this, this.constructor);

    this.type = type;
  }

  get name() {
    return this.constructor.name;
  }

  get [Symbol.toStringTag]() {
    return this.constructor.name;
  }
}

class FetchError extends FetchBaseError {
  constructor(message, type, systemError) {
    super(message, type);
    if (systemError) {
      this.code = this.errno = systemError.code;
      this.erroredSysCall = systemError.syscall;
    }
  }
}

class AbortError extends FetchBaseError {
  constructor(message, type = "aborted") {
    super(message, type);
  }
}

exports.oops = (i) => {
  if (i > 0) {
    throw new FetchError("positive number");
  } else if (i < 0) {
    throw new AbortError("negative number");
  } else {
    throw new Error("zero");
  }
};

Then a little example rescript (didn’t bother with async, but you get the idea). The Error module could be made specific for the node-fetch, or for all js exceptions, whatever you want to do. OrError module isn’t strictly necessary, just imagining a nice way to work with them.

@module("./ThrowIt")
external oops: int => unit = "oops"

module Error = {
  // Model this however you want...you could even carry around the orig js exn
  // if you want to reraise it.
  type t = [
    | #FetchError(option<string>)
    | #AbortError(option<string>)
    | #Error(option<string>)
    | #Other(option<string>)
  ]

  // Convert from js exn
  let ofJsExn = jsExn => {
    let msg = Js.Exn.message(jsExn)

    switch Js.Exn.name(jsExn) {
    | Some("FetchError") => #FetchError(msg)
    | Some("AbortError") => #AbortError(msg)
    | Some("Error") => #Error(msg)
    | _ => #UnknownError(msg)
    }
  }
}

module OrError = {
  type t<'a> = Result.t<'a, Error.t>

  // Adjust for your async needs...
  let try_with = f =>
    try {
      Ok(f())
    } catch {
    | Js.Exn.Error(obj) => Error(Error.ofJsExn(obj))
    }
}

let oops = i => OrError.try_with(() => oops(i))

// Silly example of dealing with just the FetchError
exception Fail
let _ = switch oops(10) {
| Ok() => ()
| Error(#FetchError(_)) => Console.log("caught!")
| _ => raise(Fail)
}

// Handle errors as needed...
oops(1)->Console.log
oops(0)->Console.log
oops(-1)->Console.log

Of course, you can choose to model the errors however you want…polyvariants, variants, string, exn, whatever you like.

1 Like