about summary refs log tree commit diff stats
path: root/src/js/javascript.nim
diff options
context:
space:
mode:
authorbptato <nincsnevem662@gmail.com>2023-06-19 18:13:10 +0200
committerbptato <nincsnevem662@gmail.com>2023-06-19 18:15:09 +0200
commit17097052794aef56bbc55327d3e6c84ae1c67378 (patch)
tree13b81e1105c07c69d7a8d1e7367a698f41663a01 /src/js/javascript.nim
parente372bdaa0344b23c91aefa4da44c578fbf8f49e2 (diff)
downloadchawan-17097052794aef56bbc55327d3e6c84ae1c67378.tar.gz
Rework JS exception system
Now we use Result for passing exceptions to JS. As a result, we can
finally get rid of the .jserr pragma.
Diffstat (limited to 'src/js/javascript.nim')
-rw-r--r--src/js/javascript.nim342
1 files changed, 222 insertions, 120 deletions
diff --git a/src/js/javascript.nim b/src/js/javascript.nim
index deda1ca5..fb020e5c 100644
--- a/src/js/javascript.nim
+++ b/src/js/javascript.nim
@@ -31,9 +31,11 @@ import tables
 import unicode
 
 import io/promise
+import utils/opt
 
 import bindings/quickjs
 
+export opt
 export options
 
 export
@@ -60,6 +62,19 @@ when sizeof(int) < sizeof(int64):
   export quickjs.`==`
 
 type
+  JSErrorEnum* = enum
+    # QuickJS internal errors
+    JS_EVAL_ERROR0 = "EvalError"
+    JS_RANGE_ERROR0 = "RangeError"
+    JS_REFERENCE_ERROR0 = "ReferenceError"
+    JS_SYNTAX_ERROR0 = "SyntaxError"
+    JS_TYPE_ERROR0 = "TypeError"
+    JS_URI_ERROR0 = "URIError"
+    JS_INTERNAL_ERROR0 = "InternalError"
+    JS_AGGREGATE_ERROR0 = "AggregateError"
+    # Chawan errors
+    JS_DOM_EXCEPTION = "DOMException"
+
   JSContextOpaque* = ref object
     creg: Table[string, JSClassID]
     typemap: Table[pointer, JSClassID]
@@ -72,6 +87,7 @@ type
     next: JSAtom
     value: JSAtom
     Array_prototype_values: JSValue
+    err_ctors: array[JSErrorEnum, JSValue]
 
   JSRuntimeOpaque* = ref object
     plist: Table[pointer, pointer] # Nim, JS
@@ -85,6 +101,30 @@ type
 
   JSFunctionList* = openArray[JSCFunctionListEntry]
 
+  JSError* = ref object of RootObj
+    e*: JSErrorEnum
+    message*: string
+
+  LegacyJSError* = object of CatchableError
+
+  #TODO remove these
+  JS_SyntaxError* = object of LegacyJSError
+  JS_TypeError* = object of LegacyJSError
+  JS_ReferenceError* = object of LegacyJSError
+  JS_RangeError* = object of LegacyJSError
+  JS_InternalError* = object of LegacyJSError
+
+const QuickJSErrors = [
+  JS_EVAL_ERROR0,
+  JS_RANGE_ERROR0,
+  JS_REFERENCE_ERROR0,
+  JS_SYNTAX_ERROR0,
+  JS_TYPE_ERROR0,
+  JS_URI_ERROR0,
+  JS_INTERNAL_ERROR0,
+  JS_AGGREGATE_ERROR0
+]
+
 func getOpaque*(ctx: JSContext): JSContextOpaque =
   return cast[JSContextOpaque](JS_GetContextOpaque(ctx))
 
@@ -134,11 +174,15 @@ proc newJSContext*(rt: JSRuntime): JSContext =
         let s = "next"
         opaque.next = JS_NewAtomLen(ctx, cstring(s), csize_t(s.len))
       block:
-        # 2 - JS_CLASS_ARRAY
-        let arrproto = JS_GetClassProto(ctx, 2)
-        opaque.Array_prototype_values = JS_GetPropertyStr(ctx, arrproto, "values")
+        let arrproto = JS_GetClassProto(ctx, JS_CLASS_ARRAY)
+        opaque.Array_prototype_values = JS_GetPropertyStr(ctx, arrproto,
+          "values")
         JS_FreeValue(ctx, arrproto)
       JS_FreeValue(ctx, sym)
+    for e in JSErrorEnum:
+      let s = $e
+      let err = JS_GetPropertyStr(ctx, global, cstring(s))
+      opaque.err_ctors[e] = err
     JS_FreeValue(ctx, global)
 
   JS_SetContextOpaque(ctx, cast[pointer](opaque))
@@ -172,6 +216,8 @@ proc free*(ctx: var JSContext) =
     JS_FreeAtom(ctx, opaque.done)
     JS_FreeAtom(ctx, opaque.next)
     JS_FreeValue(ctx, opaque.Array_prototype_values)
+    for v in opaque.err_ctors:
+      JS_FreeValue(ctx, v)
     GC_unref(opaque)
   JS_FreeContext(ctx)
   ctx = nil
@@ -218,7 +264,7 @@ func getOpaque*(ctx: JSContext, val: JSValue, class: string): pointer =
   # This needs further investigation.
   if ctx.isGlobal(class):
     let global = JS_GetGlobalObject(ctx)
-    let opaque = JS_GetOpaque(global, 1) # JS_CLASS_OBJECT
+    let opaque = JS_GetOpaque(global, JS_CLASS_OBJECT)
     JS_FreeValue(ctx, global)
     return opaque
   return getOpaque0(val)
@@ -229,7 +275,7 @@ func getOpaque*[T](ctx: JSContext, val: JSValue): pointer =
 proc setInterruptHandler*(rt: JSRuntime, cb: JSInterruptHandler, opaque: pointer = nil) =
   JS_SetInterruptHandler(rt, cb, opaque)
 
-func toString*(ctx: JSContext, val: JSValue): Option[string] =
+func toString*(ctx: JSContext, val: JSValue): Opt[string] =
   var plen: csize_t
   let outp = JS_ToCStringLen(ctx, addr plen, val) # cstring
   if outp != nil:
@@ -237,7 +283,7 @@ func toString*(ctx: JSContext, val: JSValue): Option[string] =
     if plen != 0:
       prepareMutation(ret)
       copyMem(addr ret[0], outp, plen)
-    result = some(ret)
+    result = ok(ret)
     JS_FreeCString(ctx, outp)
 
 proc writeException*(ctx: JSContext, s: Stream) =
@@ -297,10 +343,9 @@ proc definePropertyCWE*[T](ctx: JSContext, this: JSValue, name: string,
     definePropertyCWE(ctx, this, name, toJS(ctx, prop))
 
 func newJSClass*(ctx: JSContext, cdef: JSClassDefConst, tname: string,
-                 ctor: JSCFunction, funcs: JSFunctionList, nimt: pointer,
-                 parent: JSClassID, asglobal: bool, nointerface: bool,
-                 finalizer: proc(val: JSValue),
-                 namespace: JSValue): JSClassID {.discardable.} =
+    ctor: JSCFunction, funcs: JSFunctionList, nimt: pointer, parent: JSClassID,
+    asglobal: bool, nointerface: bool, finalizer: proc(val: JSValue),
+    namespace: JSValue, errid: Opt[JSErrorEnum]): JSClassID {.discardable.} =
   let rt = JS_GetRuntime(ctx)
   discard JS_NewClassID(addr result)
   var ctxOpaque = ctx.getOpaque()
@@ -339,6 +384,8 @@ func newJSClass*(ctx: JSContext, cdef: JSClassDefConst, tname: string,
     JS_FreeValue(ctx, global)
   let jctor = ctx.newJSCFunction($cdef.class_name, ctor, 0, JS_CFUNC_constructor)
   JS_SetConstructor(ctx, jctor, proto)
+  if errid.isSome:
+    ctx.getOpaque().err_ctors[errid.get] = JS_DupValue(ctx, jctor)
   ctxOpaque.ctors[result] = JS_DupValue(ctx, jctor)
   if not nointerface:
     if JS_IsNull(namespace):
@@ -362,41 +409,41 @@ func getMinArgs(params: seq[FuncParam]): int =
         return i
   return params.len
 
-func fromJSInt[T: SomeInteger](ctx: JSContext, val: JSValue): Option[T] =
+func fromJSInt[T: SomeInteger](ctx: JSContext, val: JSValue): Opt[T] =
   when T is int:
     # Always int32, so we don't risk 32-bit only breakage.
     # If int64 is needed, specify it explicitly.
     var ret: int32
     if JS_ToInt32(ctx, addr ret, val) < 0:
-      return none(T)
-    return some(int(ret))
+      return err()
+    return ok(int(ret))
   elif T is uint:
     var ret: uint32
     if JS_ToUint32(ctx, addr ret, val) < 0:
-      return none(T)
-    return some(uint(ret))
+      return err()
+    return ok(uint(ret))
   elif T is int32:
     var ret: int32
     if JS_ToInt32(ctx, addr ret, val) < 0:
-      return none(T)
-    return some(ret)
+      return err()
+    return ok(ret)
   elif T is int64:
     var ret: int64
     if JS_ToInt64(ctx, addr ret, val) < 0:
-      return none(T)
-    return some(ret)
+      return err()
+    return ok(ret)
   elif T is uint32:
     var ret: uint32
     if JS_ToUint32(ctx, addr ret, val) < 0:
-      return none(T)
-    return some(ret)
+      return err()
+    return ok(ret)
   elif T is uint64:
     var ret: uint32
     if JS_ToUint32(ctx, addr ret, val) < 0:
-      return none(T)
-    return some(cast[uint64](ret))
+      return err()
+    return ok(cast[uint64](ret))
 
-proc fromJS*[T](ctx: JSContext, val: JSValue): Option[T]
+proc fromJS*[T](ctx: JSContext, val: JSValue): Opt[T]
 
 macro len(t: type tuple): int =
   let i = t.getType()[1].len - 1 # - tuple
@@ -406,182 +453,183 @@ macro fromJSTupleBody(a: tuple) =
   let len = a.getType().len - 1
   let done = ident("done")
   result = newStmtList(quote do:
-    var `done`: Option[bool])
+    var `done`: Opt[bool])
   for i in 0..<len:
     result.add(quote do:
       let next = JS_Call(ctx, next_method, it, 0, nil)
       if JS_IsException(next):
-        return none(T)
+        return err()
       defer: JS_FreeValue(ctx, next)
       let doneVal = JS_GetProperty(ctx, next, ctx.getOpaque().done)
       if JS_IsException(doneVal):
-        return none(T)
+        return err()
       defer: JS_FreeValue(ctx, doneVal)
       `done` = fromJS[bool](ctx, doneVal)
       if `done`.isnone: # exception
-        return none(T)
+        return err()
       if `done`.get:
         JS_ThrowTypeError(ctx, "Too few arguments in sequence (got %d, expected %d)", `i`, `len`)
-        return none(T)
+        return err()
       let valueVal = JS_GetProperty(ctx, next, ctx.getOpaque().value)
       if JS_IsException(valueVal):
-        return none(T)
+        return err()
       defer: JS_FreeValue(ctx, valueVal)
       let genericRes = fromJS[typeof(result.get[`i`])](ctx, valueVal)
-      if genericRes.isnone: # exception
-        return none(T)
+      if genericRes.isErr: # exception
+        return err()
       `a`[`i`] = genericRes.get
     )
     if i == len - 1:
       result.add(quote do:
         let next = JS_Call(ctx, next_method, it, 0, nil)
         if JS_IsException(next):
-          return none(T)
+          return err()
         defer: JS_FreeValue(ctx, next)
         let doneVal = JS_GetProperty(ctx, next, ctx.getOpaque().done)
         `done` = fromJS[bool](ctx, doneVal)
         if `done`.isnone: # exception
-          return none(T)
+          return err()
         var i = `i`
         # we're emulating a sequence, so we must query all remaining parameters too:
         while not `done`.get:
           inc i
           let next = JS_Call(ctx, next_method, it, 0, nil)
           if JS_IsException(next):
-            return none(T)
+            return err()
           defer: JS_FreeValue(ctx, next)
           let doneVal = JS_GetProperty(ctx, next, ctx.getOpaque().done)
           if JS_IsException(doneVal):
-            return none(T)
+            return err()
           defer: JS_FreeValue(ctx, doneVal)
           `done` = fromJS[bool](ctx, doneVal)
           if `done`.isnone: # exception
-            return none(T)
+            return err()
           if `done`.get:
             JS_ThrowTypeError(ctx, "Too many arguments in sequence (got %d, expected %d)", i, `len`)
-            return none(T)
+            return err()
           JS_FreeValue(ctx, JS_GetProperty(ctx, next, ctx.getOpaque().value))
       )
 
-proc fromJSTuple[T: tuple](ctx: JSContext, val: JSValue): Option[T] =
+proc fromJSTuple[T: tuple](ctx: JSContext, val: JSValue): Opt[T] =
   let itprop = JS_GetProperty(ctx, val, ctx.getOpaque().sym_iterator)
   if JS_IsException(itprop):
-    return none(T)
+    return err()
   defer: JS_FreeValue(ctx, itprop)
   let it = JS_Call(ctx, itprop, val, 0, nil)
   if JS_IsException(it):
-    return none(T)
+    return err()
   defer: JS_FreeValue(ctx, it)
   let next_method = JS_GetProperty(ctx, it, ctx.getOpaque().next)
   if JS_IsException(next_method):
-    return none(T)
+    return err()
   defer: JS_FreeValue(ctx, next_method)
   var x: T
   fromJSTupleBody(x)
-  return some(x)
+  return ok(x)
 
-proc fromJSSeq[T](ctx: JSContext, val: JSValue): Option[seq[T]] =
+proc fromJSSeq[T](ctx: JSContext, val: JSValue): Opt[seq[T]] =
   let itprop = JS_GetProperty(ctx, val, ctx.getOpaque().sym_iterator)
   if JS_IsException(itprop):
-    return none(seq[T])
+    return err()
   defer: JS_FreeValue(ctx, itprop)
   let it = JS_Call(ctx, itprop, val, 0, nil)
   if JS_IsException(it):
-    return none(seq[T])
+    return err()
   defer: JS_FreeValue(ctx, it)
   let next_method = JS_GetProperty(ctx, it, ctx.getOpaque().next)
   if JS_IsException(next_method):
-    return none(seq[T])
+    return err()
   defer: JS_FreeValue(ctx, next_method)
-  result = some(newSeq[T]())
+  result = ok(newSeq[T]())
   while true:
     let next = JS_Call(ctx, next_method, it, 0, nil)
     if JS_IsException(next):
-      return none(seq[T])
+      return err()
     defer: JS_FreeValue(ctx, next)
     let doneVal = JS_GetProperty(ctx, next, ctx.getOpaque().done)
     if JS_IsException(doneVal):
-      return none(seq[T])
+      return err()
     defer: JS_FreeValue(ctx, doneVal)
     let done = fromJS[bool](ctx, doneVal)
     if done.isnone: # exception
-      return none(seq[T])
+      return err()
     if done.get:
       break
     let valueVal = JS_GetProperty(ctx, next, ctx.getOpaque().value)
     if JS_IsException(valueVal):
-      return none(seq[T])
+      return err()
     defer: JS_FreeValue(ctx, valueVal)
     let genericRes = fromJS[typeof(result.get[0])](ctx, valueVal)
     if genericRes.isnone: # exception
-      return none(seq[T])
+      return err()
     result.get.add(genericRes.get)
 
-proc fromJSSet[T](ctx: JSContext, val: JSValue): Option[set[T]] =
+proc fromJSSet[T](ctx: JSContext, val: JSValue): Opt[set[T]] =
   let itprop = JS_GetProperty(ctx, val, ctx.getOpaque().sym_iterator)
   if JS_IsException(itprop):
-    return none(set[T])
+    return err()
   defer: JS_FreeValue(ctx, itprop)
   let it = JS_Call(ctx, itprop, val, 0, nil)
   if JS_IsException(it):
-    return none(set[T])
+    return err()
   defer: JS_FreeValue(ctx, it)
   let next_method = JS_GetProperty(ctx, it, ctx.getOpaque().next)
   if JS_IsException(next_method):
-    return none(set[T])
+    return err()
   defer: JS_FreeValue(ctx, next_method)
   var s: set[T]
   result = some(s)
   while true:
     let next = JS_Call(ctx, next_method, it, 0, nil)
     if JS_IsException(next):
-      return none(set[T])
+      return err()
     defer: JS_FreeValue(ctx, next)
     let doneVal = JS_GetProperty(ctx, next, ctx.getOpaque().done)
     if JS_IsException(doneVal):
-      return none(set[T])
+      return err()
     defer: JS_FreeValue(ctx, doneVal)
     let done = fromJS[bool](ctx, doneVal)
     if done.isnone: # exception
-      return none(set[T])
+      return err()
     if done.get:
       break
     let valueVal = JS_GetProperty(ctx, next, ctx.getOpaque().value)
     if JS_IsException(valueVal):
-      return none(set[T])
+      return err()
     defer: JS_FreeValue(ctx, valueVal)
     let genericRes = fromJS[typeof(result.get.items)](ctx, valueVal)
     if genericRes.isnone: # exception
-      return none(set[T])
+      return err()
     result.get.incl(genericRes.get)
 
-proc fromJSTable[A, B](ctx: JSContext, val: JSValue): Option[Table[A, B]] =
+proc fromJSTable[A, B](ctx: JSContext, val: JSValue): Opt[Table[A, B]] =
   var ptab: ptr JSPropertyEnum
   var plen: uint32
   let flags = cint(JS_GPN_STRING_MASK)
   if JS_GetOwnPropertyNames(ctx, addr ptab, addr plen, val, flags) == -1:
     # exception
-    return none(Table[A, B])
+    return err()
   defer:
     for i in 0..<plen:
       let prop = cast[ptr JSPropertyEnum](cast[int](ptab) + sizeof(ptab[]) * int(i))
       JS_FreeAtom(ctx, prop.atom)
     js_free(ctx, ptab)
-  result = some(Table[A, B]())
+  var res = Table[A, B]()
   for i in 0..<plen:
     let prop = cast[ptr JSPropertyEnum](cast[int](ptab) + sizeof(ptab[]) * int(i))
     let atom = prop.atom
     let k = JS_AtomToValue(ctx, atom)
     defer: JS_FreeValue(ctx, k)
     let kn = fromJS[A](ctx, k)
-    if kn.isnone: # exception
-      return none(Table[A, B])
+    if kn.isErr: # exception
+      return err()
     let v = JS_GetProperty(ctx, val, atom)
     defer: JS_FreeValue(ctx, v)
     let vn = fromJS[B](ctx, v)
-    if vn.isnone: # exception
-      return none(Table[A, B])
+    if vn.isErr: # exception
+      return err()
     result.get[kn.get] = vn.get
+  return ok(res)
 
 proc toJS*(ctx: JSContext, s: cstring): JSValue
 proc toJS*(ctx: JSContext, s: string): JSValue
@@ -595,17 +643,21 @@ proc toJS*(ctx: JSContext, n: uint64): JSValue
 proc toJS(ctx: JSContext, n: SomeFloat): JSValue
 proc toJS*(ctx: JSContext, b: bool): JSValue
 proc toJS[U, V](ctx: JSContext, t: Table[U, V]): JSValue
-proc toJS(ctx: JSContext, opt: Option): JSValue
+proc toJS*(ctx: JSContext, opt: Option): JSValue
+proc toJS[T, E](ctx: JSContext, opt: Result[T, E]): JSValue
 proc toJS(ctx: JSContext, s: seq): JSValue
 proc toJS(ctx: JSContext, e: enum): JSValue
 proc toJS(ctx: JSContext, j: JSValue): JSValue
 proc toJS[T](ctx: JSContext, promise: Promise[T]): JSValue
+proc toJS[T, E](ctx: JSContext, promise: Promise[Result[T, E]]): JSValue
 proc toJS(ctx: JSContext, promise: EmptyPromise): JSValue
+proc toJSRefObj(ctx: JSContext, obj: ref object): JSValue
 proc toJS*(ctx: JSContext, obj: ref object): JSValue
+proc toJS*(ctx: JSContext, err: JSError): JSValue
 
 # ew....
-proc fromJSFunction1[T, U](ctx: JSContext, val: JSValue): Option[proc(x: U): Option[T]] =
-  return some(proc(x: U): Option[T] =
+proc fromJSFunction1[T, U](ctx: JSContext, val: JSValue): Opt[proc(x: U): Opt[T]] =
+  return ok(proc(x: U): Opt[T] =
     var arg1 = toJS(ctx, x)
     let ret = JS_Call(ctx, val, JS_UNDEFINED, 1, addr arg1)
     return fromJS[T](ctx, ret)
@@ -617,7 +669,7 @@ macro unpackReturnType(f: typed) =
     x = x[1].getTypeImpl()
   let params = x.findChild(it.kind == nnkFormalParams)
   let rv = params[0]
-  assert rv[0].strVal == "Option"
+  doAssert rv[0].strVal == "Opt"
   let rvv = rv[1]
   result = quote do: `rvv`
 
@@ -631,7 +683,7 @@ macro unpackArg0(f: typed) =
   let rvv = rv[1]
   result = quote do: `rvv`
 
-proc fromJS*[T](ctx: JSContext, val: JSValue): Option[T] =
+proc fromJS*[T](ctx: JSContext, val: JSValue): Opt[T] =
   when T is string:
     return toString(ctx, val)
   elif T is char:
@@ -653,14 +705,22 @@ proc fromJS*[T](ctx: JSContext, val: JSValue): Option[T] =
     return some(r)
   elif T is (proc):
     return fromJSFunction1[typeof(unpackReturnType(T)), typeof(unpackArg0(T))](ctx, val)
-  elif typeof(result.unsafeGet) is Option: # unwrap
+  elif T is Option: # convert
     if JS_IsUndefined(val):
       #TODO what about null?
-      return none(T)
+      return err()
     let res = fromJS[typeof(result.get.get)](ctx, val)
     if res.isNone:
-      return none(T)
-    return some(res)
+      return err()
+    return ok(some(res.get))
+  elif typeof(result).valType is Opt: # unwrap
+    if JS_IsUndefined(val):
+      #TODO what about null?
+      return err()
+    let res = fromJS[typeof(result.get.get)](ctx, val)
+    if res.isNone:
+      return err()
+    return ok(res)
   elif T is seq:
     return fromJSSeq[typeof(result.get.items)](ctx, val)
   elif T is set:
@@ -670,11 +730,11 @@ proc fromJS*[T](ctx: JSContext, val: JSValue): Option[T] =
   elif T is bool:
     let ret = JS_ToBool(ctx, val)
     if ret == -1: # exception
-      return none(T)
+      return err()
     if ret == 0:
-      return some(false)
-    return some(true)
-  elif typeof(result.get) is Table:
+      return ok(false)
+    return ok(true)
+  elif typeof(result).valType is Table:
     return fromJSTable[typeof(result.get.keys), typeof(result.get.values)](ctx, val)
   elif T is SomeInteger:
     if JS_IsNumber(val):
@@ -683,50 +743,50 @@ proc fromJS*[T](ctx: JSContext, val: JSValue): Option[T] =
     if JS_IsNumber(val):
       var f64: float64
       if JS_ToFloat64(ctx, addr f64, val) < 0:
-        return none(T)
-      return some(cast[T](f64))
+        return err()
+      return ok(cast[T](f64))
   elif T is enum:
     #TODO implement enum handling...
     if JS_IsException(val):
-      return none(T)
+      return err()
     let s = toString(ctx, val)
-    if s.isnone:
-      return none(T)
+    if s.isErr:
+      return err()
     try:
-      return some(parseEnum[T](s.get))
+      return ok(parseEnum[T](s.get))
     except ValueError:
       JS_ThrowTypeError(ctx, "`%s' is not a valid value for enumeration %s", cstring(s.get), $T)
-      return none(T)
+      return err()
   elif T is JSValue:
-    return some(val)
-  elif T is object:
-    doAssert false, "Dictionary case has not been implemented yet!"
-    #TODO TODO TODO implement dictionary case
-    return none(T)
-  else:
+    return ok(val)
+  elif T is ref object:
     if JS_IsException(val):
-      return none(T)
+      return err()
     let op = cast[T](getOpaque(ctx, val, $T))
     if op == nil:
       JS_ThrowTypeError(ctx, "Value is not an instance of %s", $T)
-      return none(T)
-    return some(op)
+      return err()
+    return ok(op)
+  else:
+    static:
+      doAssert false
 
 const JS_ATOM_TAG_INT = cuint(1u32 shl 31)
 
 func JS_IsNumber(v: JSAtom): JS_BOOL =
   return (cast[cuint](v) and JS_ATOM_TAG_INT) != 0
 
-func fromJS[T: string|uint32](ctx: JSContext, atom: JSAtom): Option[T] =
+func fromJS[T: string|uint32](ctx: JSContext, atom: JSAtom): Opt[T] =
   when T is SomeNumber:
     if JS_IsNumber(atom):
-      return some(T(cast[uint32](atom) and (not JS_ATOM_TAG_INT)))
+      return ok(T(cast[uint32](atom) and (not JS_ATOM_TAG_INT)))
   else:
     let val = JS_AtomToValue(ctx, atom)
     return fromJS[T](ctx, val)
 
-proc getJSFunction*[T, U](ctx: JSContext, val: JSValue): Option[(proc(x: T): Option[U])] =
-  return fromJS[(proc(x: T): Option[U])](ctx, val)
+proc getJSFunction*[T, U](ctx: JSContext, val: JSValue):
+    Opt[(proc(x: T): Opt[U])] =
+  return fromJS[(proc(x: T): Opt[U])](ctx, val)
 
 proc toJS*(ctx: JSContext, s: cstring): JSValue =
   return JS_NewString(ctx, s)
@@ -770,11 +830,23 @@ proc toJS[U, V](ctx: JSContext, t: Table[U, V]): JSValue =
       setProperty(ctx, obj, k, toJS(ctx, v))
   return obj
 
-proc toJS(ctx: JSContext, opt: Option): JSValue =
+proc toJS*(ctx: JSContext, opt: Option): JSValue =
   if opt.isSome:
     return toJS(ctx, opt.get)
   return JS_NULL
 
+proc toJS[T, E](ctx: JSContext, opt: Result[T, E]): JSValue =
+  if opt.isSome:
+    when not (T is void):
+      return toJS(ctx, opt.get)
+    return JS_UNDEFINED
+  else:
+    when not (E is void):
+      let res = toJS(ctx, opt.error)
+      if not JS_IsNull(res):
+        return JS_Throw(ctx, res)
+    return JS_NULL
+
 proc toJS(ctx: JSContext, s: seq): JSValue =
   let a = JS_NewArray(ctx)
   if not JS_IsException(a):
@@ -794,7 +866,7 @@ proc getTypePtr[T](x: T): pointer =
   else:
     return getTypeInfo(x)
 
-proc toJS*(ctx: JSContext, obj: ref object): JSValue =
+proc toJSRefObj(ctx: JSContext, obj: ref object): JSValue =
   if obj == nil:
     return JS_NULL
   let op = JS_GetRuntime(ctx).getOpaque()
@@ -807,6 +879,9 @@ proc toJS*(ctx: JSContext, obj: ref object): JSValue =
   setOpaque(ctx, jsObj, obj)
   return jsObj
 
+proc toJS*(ctx: JSContext, obj: ref object): JSValue =
+  return toJSRefObj(ctx, obj)
+
 proc toJS(ctx: JSContext, e: enum): JSValue =
   return toJS(ctx, $e)
 
@@ -840,15 +915,42 @@ proc toJS[T](ctx: JSContext, promise: Promise[T]): JSValue =
     JS_FreeValue(ctx, resolving_funcs[1]))
   return jsPromise
 
-type
-  JS_Error = object of CatchableError
+proc toJS[T, E](ctx: JSContext, promise: Promise[Result[T, E]]): JSValue =
+  var resolving_funcs: array[2, JSValue]
+  let jsPromise = JS_NewPromiseCapability(ctx, addr resolving_funcs[0])
+  if JS_IsException(jsPromise):
+    return JS_EXCEPTION
+  promise.then(proc(x: Result[T, E]) =
+    if x.isOk:
+      let x = when T is void:
+        JS_UNDEFINED
+      else:
+        toJS(ctx, x.get)
+      let res = JS_Call(ctx, resolving_funcs[0], JS_UNDEFINED, 1, unsafeAddr x)
+      JS_FreeValue(ctx, res)
+      JS_FreeValue(ctx, x)
+    else: # err
+      let x = when E is void:
+        JS_UNDEFINED
+      else:
+        toJS(ctx, x.get)
+      let res = JS_Call(ctx, resolving_funcs[1], JS_UNDEFINED, 1, unsafeAddr x)
+      JS_FreeValue(ctx, res)
+      JS_FreeValue(ctx, x)
+    JS_FreeValue(ctx, resolving_funcs[0])
+    JS_FreeValue(ctx, resolving_funcs[1]))
+  return jsPromise
 
-  JS_SyntaxError* = object of JS_Error
-  JS_TypeError* = object of JS_Error
-  JS_ReferenceError* = object of JS_Error
-  JS_RangeError* = object of JS_Error
-  JS_InternalError* = object of JS_Error
+proc toJS*(ctx: JSContext, err: JSError): JSValue =
+  if err.e notin QuickJSErrors:
+    return toJSRefObj(ctx, err)
+  var msg = toJS(ctx, err.message)
+  if JS_IsException(msg):
+    return msg
+  let ctor = ctx.getOpaque().err_ctors[err.e]
+  return JS_CallConstructor(ctx, ctor, 1, addr msg)
 
+type
   JSFuncGenerator = object
     t: BoundFunctionType
     original: NimNode
@@ -1020,7 +1122,7 @@ template fromJS_or_die*(t, ctx, val, ev, dl: untyped): untyped =
     if JS_IsException(val):
       return ev
   let x = fromJS[t](ctx, val)
-  if x.isnone:
+  if x.isNone:
     break dl
   x.get
 
@@ -1248,7 +1350,7 @@ proc newJSProcBody(gen: var JSFuncGenerator, isva: bool): NimNode =
   var ma = gen.actualMinArgs
   result = newStmtList()
   if isva:
-    result.add(quote do: 
+    result.add(quote do:
       if argc < `ma`:
         return JS_ThrowTypeError(ctx, "At least %d arguments required, " &
           "but only %d passed", `ma`, argc)
@@ -1288,7 +1390,7 @@ proc newJSProc(gen: var JSFuncGenerator, params: openArray[NimNode], isva = true
 # declared on the parent function.
 # Note: this causes the entire nim function body to be inlined inside the JS
 # interface function.
-#TODO: implement actual inlining (so we can e.g. get rid of JS_Error, use format strings, etc.)
+#TODO: remove this.
 macro JS_ERR*(a: typed, b: string) =
   result = quote do:
     block when_js:
@@ -1379,7 +1481,7 @@ proc rewriteExceptions(gen: var JSFuncGenerator, errors: var seq[string], node:
         errors.add(c[1].strVal)
     elif c.len > 0:
       gen.rewriteExceptions(errors, c)
-  
+
 proc rewriteExceptions(gen: var JSFuncGenerator) =
   let ostmts = gen.original.findChild(it.kind == nnkStmtList)
   var errors: seq[string]
@@ -1700,10 +1802,10 @@ type
     name*: string
     fun*: JSCFunction
 
-macro registerType*(ctx: typed, t: typed, parent: JSClassID = 0, asglobal =
-                   false, nointerface = false, name: static string = "",
-                   extra_getset: static openarray[TabGetSet] = [],
-                   namespace: JSValue = JS_NULL): JSClassID =
+macro registerType*(ctx: typed, t: typed, parent: JSClassID = 0,
+    asglobal = false, nointerface = false, name: static string = "",
+    extra_getset: static openarray[TabGetSet] = [],
+    namespace: JSValue = JS_NULL, errid = opt(JSErrorEnum)): JSClassID =
   result = newStmtList()
   let tname = t.strVal # the nim type's name.
   let name = if name == "": tname else: name # possibly a different name, e.g. Buffer for Container
@@ -1868,7 +1970,7 @@ static JSClassDef """, `cdname`, """ = {
     var x: `t`
     new(x, nim_finalize_for_js)
     `ctx`.newJSClass(`classDef`, `tname`, `sctr`, `tabList`, getTypePtr(x),
-      `parent`, `asglobal`, `nointerface`, `finName`, `namespace`)
+      `parent`, `asglobal`, `nointerface`, `finName`, `namespace`, `errid`)
   )
   result.add(newBlockStmt(endstmts))