异常

异常只是一种特殊的变体,在出现异常的情况下抛出(不要滥用它们!)。

用法

ReScriptJS Output
let getItem = (items) =>
  if callSomeFunctionThatThrows() {
    // return the found item here
    1
  } else {
    raise(Not_found)
  }

let result =
  try {
    getItem([1, 2, 3])
  } catch {
  | Not_found => 0 // Default value if getItem throws
  }

注意,上面的代码只是为了演示;事实上,你可以直接从 getItem 返回一个 option<int>,并完全避免使用 try

你可以在匹配异常的 同时 从函数获取返回值:

ReScriptJS Output
switch List.find(i => i === theItem, myItems) {
| item => Js.log(item)
| exception Not_found => Js.log("No such item found!")
}

你也可以像创建变体一样创建你自己的异常(异常也需要首字母大写)。

ReScriptJS Output
exception InputClosed(string)
// later on
raise(InputClosed("The stream has closed!"))

捕获 JS 异常

为了区分 JavaScript 异常和 ReScript 异常,ReScript 将 JS 异常限制在 Js.Exn.Error(payload) 变体下。捕获一个从 JS 端抛出的异常:

ReScriptJS Output
try {
  someJSFunctionThatThrows()
} catch {
| Js.Exn.Error(obj) =>
  switch Js.Exn.message(obj) {
  | Some(m) => Js.log("Caught a JS exception! Message: " ++ m)
  | None => ()
  }
}

obj 的类型是 Js.Exn.t,ReScript 故意让这个类型不透明来阻止一些非法操作。如果要对 obj 进行操作,可以像上面的代码一样使用标准库 Js.Exn 模块中的辅助函数。

抛出一个 JS 异常

raise(MyException) 会引发一个 ReScript 异常。要引发一个 JavaScript 异常(不管你的目的是什么),请使用 Js.Exn.raiseError

ReScriptJS Output
let myTest = () => {
  Js.Exn.raiseError("Hello!")
}

你可以在 JS 端捕获它:

JS
// after importing `myTest`... try { myTest() } catch (e) { console.log(e.message) // "Hello!" }

在 JS 中捕获 ReScript 异常

前面的部分没有你想象的那么有用;为了让你的 JS 代码与抛出异常的 ReScript 代码一起工作,后者实际上不需要抛出一个 JS 异常。ReScript 的异常可以被 JS 代码使用!

ReScriptJS Output
exception BadArgument({myMessage: string})

let myTest = () => {
  raise(BadArgument({myMessage: "Oops!"}))
}

然后在 JS 中:

JS
// after importing `myTest`... try { myTest() } catch (e) { console.log(e.myMessage) // "Oops!" console.log(e.Error.stack) // the stack trace }

注意:RE_EXN_ID 是一个用于“记录”的内部字段。不要在 JS 端使用它。使用其他的字段。

上面的 BadArgument 异常接受一个内联记录类型。为了符合人体工程学(指用起来更符合直觉),我们将该异常编译为 {RE_EXN_ID, myMessage, Error}。如果异常接受普通的位置参数,比如标准库的 Invalid_argument("Oops!"),它只使用一个参数,那么这个参数将被编译为 JS 的字段 _1,第二个位置参数会编译为_2,以此类推。

技巧和诀窍

当你有普通的变体时,你往往不需要异常。例如,当 item 在一个集合无法找到时,可以返回一个 option<item>(这种情况返回 None)而不是抛出异常。

在一个 catch 子句中同时捕获 ReScript 和 JS 的异常

ReScriptJS Output
try {
  someOtherJSFunctionThatThrows()
} catch {
| Not_found => ... // catch a ReScript exception
| Invalid_argument(_) => ... // catch a second ReScript exception
| Js.Exn.Error(obj) => ... // catch the JS exception
}

这在技术上是可行的,但希望你永远不必与这样的代码打交道......