Exploring ReScript Exception Handling
Tags: rescript
Published: 2020-11-21 06:13:47

# Introduction

A brief exploration of some ReScript exception handling.

Hopefully this provides some useful examples of ReScript error handling.

ReScript version: `bs-platform@8.3.3` 

# Handling JavaScript exceptions thrown from JavaScript

Write a JS function that throws an error, and add a binding for it.

```js
%%raw(`
function a() {
  throw new Error("An error occurred")
}
`)

@bs.val external a: unit => unit = "a"
```

Catch the error using try/catch.

```js
try {
  a()
} catch {
| Js.Exn.Error(error) => {
    // error: Js.Exn.t
    Js.log(Js.Exn.name(error))
    Js.log(Js.Exn.message(error))
  }
}
```

Or, catch the error using `switch` and matching on the `Js.Exn.Error` type:

```js
switch a() {
| exception Js.Exn.Error(error) => {
    // error: Js.Exn.t
    Js.log(Js.Exn.name(error))
    Js.log(Js.Exn.message(error))
  }
| () => ()
}
```

Or, catch the error using `switch`, matching on any exception type, and detect the `Js.Exn.t` type.

```js
switch a() {
| exception error => {
    // error: exn
    let optError = Js.Exn.asJsExn(error)
    switch optError {
    | Some(error) => {
        // error: Js.Exn.t
        Js.log(Js.Exn.name(error))
        Js.log(Js.Exn.message(error))
      }
    | None => Js.log("Unknown error")
    }
  }
| () => ()
}
```

# Handling JavaScript exceptions thrown from ReScript

Write a function that throws a JavaScript exception:

```js
let b = (): unit => {
  // Equivalent to throw new Error("An error occurred")
  Js.Exn.raiseError("An error occurred")
}
```

*Handling these errors is identical to when the exception is thrown from JavaScript.*

Catch the error using try/catch:

```js
try {
  b()
} catch {
| Js.Exn.Error(error) => {
    // error: Js.Exn.t
    Js.log(Js.Exn.name(error))
    Js.log(Js.Exn.message(error))
  }
}
```

Or, catch the error using `switch` and matching on the `Js.Exn.Error` type:

```js
switch b() {
| exception Js.Exn.Error(error) => {
    Js.log(Js.Exn.name(error))
    Js.log(Js.Exn.message(error))
  }
| () => ()
}
```

Or, catch the error using `switch`, matching on any exception type, and detect the `Js.Exn.t` type.

```js
switch b() {
| exception error => {
    // error: exn
    let optError = Js.Exn.asJsExn(error)
    switch optError {
    | Some(error) => {
        // error: Js.Exn.t
        Js.log(Js.Exn.name(error))
        Js.log(Js.Exn.message(error))
      }
    | None => Js.log("Unknown error")
    }
  }
| () => ()
}
```

# Handling ReScript exceptions

Write a function that throws a custom ReScript exception:

```js
exception MyException(string)

let c = (): unit => {
  raise(MyException("An error occurred"))
}
```

Handle using try/catch, matching on the exception:

```js
try {
  c()
} catch {
| MyException(reason) => Js.log("MyException:" ++ reason)
}
```

Handle using `switch`, matching on the exception.

*Notice the need for the `exception` keyword here.*

```js
switch c() {
| exception MyException(reason) => Js.log("MyException:" ++ reason)
| () => ()
}
```

Handle using `switch` and try detect if it's a JavaScript error:

```js
switch c() {
| exception error => {
    // error: exn
    let optError: option<Js.Exn.t> = Js.Exn.asJsExn(error)
    switch optError {
    | Some(error) => {
        // error: Js.Exn.t
        Js.log(Js.Exn.name(error))
        Js.log(Js.Exn.message(error))
      }
    | None => Js.log("Not a JavaScript error")
    }
  }
| () => ()
}
```

*This prints "Not a JavaScript error", as expected*

# Handling promise rejections from JavaScript

Write a JavaScript function that returns a rejected promise, and add a binding for it.

```js
%%raw(`
function d() {
  return Promise.reject(new Error("An error occurred"))
}
`)

@bs.val external d: unit => Js.Promise.t<unit> = "d"
```

Catch the error and attempt to identify that it's a JavaScript error.

```js
d()->Js.Promise.catch((error: Js.Promise.error) => {
  switch error {
  | exception error => {
      let optError: option<Js.Exn.t> = Js.Exn.asJsExn(error)
      switch optError {
      | Some(error) => {
          Js.log(Js.Exn.name(error))
          Js.log(Js.Exn.message(error))
        }
      | None => Js.log("Not a Js.Exn.t")
      }
    }
  | _ => Js.log("Not an exn")
  }
  Js.Promise.resolve()
}, _)
```

This prints:

```
Not an exn
```

Notes:
* The JavaScript error is converted to type `Js.Promise.error`, which is not a member of the `exn` type.
* The switch case `exception error` match does not occur, because the error is not an `exn` type.

# Handling JavaScript promise rejections from ReScript

Write a function that returns a rejected promise containing a JavaScript error.

```js
let e = (): Js.Promise.t<unit> => {
  Js.Promise.make((~resolve as _, ~reject as _) => {
    Js.Exn.raiseError("An error occurred")
  })
}
```

*Note: Raising the error has the effect of rejecting the promise.*

Catch the error and try detect that it's a JavaScript error.

```js
e()->Js.Promise.catch((error: Js.Promise.error) => {
  switch error {
  | exception error => {
      let optError: option<Js.Exn.t> = Js.Exn.asJsExn(error)
      switch optError {
      | Some(error) => {
          Js.log(Js.Exn.name(error))
          Js.log(Js.Exn.message(error))
        }
      | None => Js.log("Not a Js.Exn.t")
      }
    }
  | _ => Js.log("Not an exn")
  }
  Js.Promise.resolve()
}, _)
```

This prints:

```
Not an exn
```

Notes:
* The JavaScript error is converted to type `Js.Promise.error`, which is not a member of the `exn` type.
* The switch case `exception error` match does not occur, because the error is not an `exn` type.

# Handling ReScript promise rejections from ReScript

Write a function that returns a rejected promise containing a Rescript error.

```js
exception MyException(string)

let f = (): Js.Promise.t<unit> => {
  Js.Promise.make((~resolve as _, ~reject as _) => {
    raise(MyException("An error occurred"))
  })
}
```

*Note: Raising the error has the effect of rejecting the promise.*

Catch the error and attempt to determine its type:

```js
f()->Js.Promise.catch((error: Js.Promise.error) => {
  switch error {
  | exception error => {
      let optError: option<Js.Exn.t> = Js.Exn.asJsExn(error)
      switch optError {
      | Some(error) => {
          Js.log(Js.Exn.name(error))
          Js.log(Js.Exn.message(error))
        }
      | None => Js.log("Not a Js.Exn.t")
      }
    }
  | _ => Js.log("Not an exn")
  }
  Js.Promise.resolve()
}, _)
```

This prints:

```
Not an exn
```

Notes:
* The ReScript error is converted to type `Js.Promise.error`, which is not a member of the `exn` type.
* The switch case `exception error` match does not occur, because the error is not an `exn` type.

# Handling ReScript promise rejections using the exn Failure variant.

The `Failure(string)` variant has an `exn` type.

Write a function that rejects a promise using a `Failure`:

```js
let g = (): Js.Promise.t<unit> => {
  Js.Promise.make((~resolve as _, ~reject) => {
    reject(. Failure("An error occurred"))
  })
}
```

Catch the error and attempt to determine its type:

```js
g()->Js.Promise.catch((error: Js.Promise.error) => {
  switch error {
  | exception error => {
      let optError: option<Js.Exn.t> = Js.Exn.asJsExn(error)
      switch optError {
      | Some(error) => {
          Js.log(Js.Exn.name(error))
          Js.log(Js.Exn.message(error))
        }
      | None => Js.log("Not a Js.Exn.t")
      }
    }
  | _ => Js.log("Not an exn")
  }
  Js.Promise.resolve()
}, _)
```

This prints:

```
Not an exn
```

Notes:
* The Failure error is converted to type `Js.Promise.error`, which is not a member of the `exn` type.
* The switch case `exception error` match does not occur, because the error is not an `exn` type.

# References

* [ReScript Exception documentation](https://rescript-lang.org/docs/manual/latest/exception)
* [How to reject a promise when using Js.Promise.make()?](https://forum.rescript-lang.org/t/how-to-reject-a-promise-when-using-js-promise-make/671/3)