summary refs log tree commit diff stats
path: root/lib/js/asyncjs.nim
diff options
context:
space:
mode:
Diffstat (limited to 'lib/js/asyncjs.nim')
-rw-r--r--lib/js/asyncjs.nim212
1 files changed, 112 insertions, 100 deletions
diff --git a/lib/js/asyncjs.nim b/lib/js/asyncjs.nim
index c62ac633f..9b043f3e5 100644
--- a/lib/js/asyncjs.nim
+++ b/lib/js/asyncjs.nim
@@ -17,37 +17,42 @@
 ##
 ## This is roughly equivalent to the `async` keyword in JavaScript code.
 ##
-## .. code-block:: nim
-##  proc loadGame(name: string): Future[Game] {.async.} =
-##    # code
+##   ```nim
+##   proc loadGame(name: string): Future[Game] {.async.} =
+##     # code
+##   ```
 ##
 ## should be equivalent to
 ##
-## .. code-block:: javascript
+##   ```javascript
 ##   async function loadGame(name) {
 ##     // code
 ##   }
+##   ```
 ##
 ## A call to an asynchronous procedure usually needs `await` to wait for
 ## the completion of the `Future`.
 ##
-## .. code-block:: nim
+##   ```nim
 ##   var game = await loadGame(name)
+##   ```
 ##
 ## Often, you might work with callback-based API-s. You can wrap them with
 ## asynchronous procedures using promises and `newPromise`:
 ##
-## .. code-block:: nim
+##   ```nim
 ##   proc loadGame(name: string): Future[Game] =
 ##     var promise = newPromise() do (resolve: proc(response: Game)):
 ##       cbBasedLoadGame(name) do (game: Game):
 ##         resolve(game)
 ##     return promise
+##   ```
 ##
 ## Forward definitions work properly, you just need to always add the `{.async.}` pragma:
 ##
-## .. code-block:: nim
+##   ```nim
 ##   proc loadGame(name: string): Future[Game] {.async.}
+##   ```
 ##
 ## JavaScript compatibility
 ## ========================
@@ -57,13 +62,14 @@
 ## If you need to use this module with older versions of JavaScript, you can
 ## use a tool that backports the resulting JavaScript code, as babel.
 
-# xxx code-block:: javascript above gives `LanguageXNotSupported` warning.
+# xxx code: javascript above gives `LanguageXNotSupported` warning.
 
 when not defined(js) and not defined(nimsuggest):
   {.fatal: "Module asyncjs is designed to be used with the JavaScript backend.".}
 
 import std/jsffi
 import std/macros
+import std/private/since
 
 type
   Future*[T] = ref object
@@ -84,6 +90,8 @@ proc replaceReturn(node: var NimNode) =
       node[z] = nnkReturnStmt.newTree(value)
     elif son.kind == nnkAsgn and son[0].kind == nnkIdent and $son[0] == "result":
       node[z] = nnkAsgn.newTree(son[0], nnkCall.newTree(jsResolve, son[1]))
+    elif son.kind in RoutineNodes:
+      discard
     else:
       replaceReturn(son)
     inc z
@@ -94,10 +102,18 @@ proc isFutureVoid(node: NimNode): bool =
            node[1].kind == nnkIdent and $node[1] == "void"
 
 proc generateJsasync(arg: NimNode): NimNode =
-  if arg.kind notin {nnkProcDef, nnkLambda, nnkMethodDef, nnkDo}:
+  if arg.kind notin {nnkProcDef, nnkLambda, nnkMethodDef, nnkDo, nnkProcTy}:
       error("Cannot transform this node kind into an async proc." &
             " proc/method definition or lambda node expected.")
 
+  # Transform type X = proc (): something {.async.}
+  # into      type X = proc (): Future[something]
+  if arg.kind == nnkProcTy:
+    result = arg
+    if arg[0][0].kind == nnkEmpty:
+      result[0][0] = quote do: Future[void]
+    return result
+
   result = arg
   var isVoid = false
   let jsResolve = ident("jsResolve")
@@ -158,100 +174,96 @@ proc newPromise*(handler: proc(resolve: proc())): Future[void] {.importjs: "(new
   ## A helper for wrapping callback-based functions
   ## into promises and async procedures.
 
-template typeOrVoid[T](a: T): type =
-  # xxx this is useful, make it public in std/typetraits in future work
-  T
-
 template maybeFuture(T): untyped =
   # avoids `Future[Future[T]]`
   when T is Future: T
   else: Future[T]
 
-when defined(nimExperimentalAsyncjsThen):
-  import std/private/since
-  since (1, 5, 1):
-    #[
-    TODO:
-    * map `Promise.all()`
-    * proc toString*(a: Error): cstring {.importjs: "#.toString()".}
-
-    Note:
-    We probably can't have a `waitFor` in js in browser (single threaded), but maybe it would be possible
-    in in nodejs, see https://nodejs.org/api/child_process.html#child_process_child_process_execsync_command_options
-    and https://stackoverflow.com/questions/61377358/javascript-wait-for-async-call-to-finish-before-returning-from-function-witho
-    ]#
-
-    type Error*  {.importjs: "Error".} = ref object of JsRoot
-      ## https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error
-      message*: cstring
-      name*: cstring
-
-    type OnReject* = proc(reason: Error)
-
-    proc then*[T](future: Future[T], onSuccess: proc, onReject: OnReject = nil): auto =
-      ## See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/then
-      ## Returns a `Future` from the return type of `onSuccess(T.default)`.
-      runnableExamples("-d:nimExperimentalAsyncjsThen"):
-        from std/sugar import `=>`
-
-        proc fn(n: int): Future[int] {.async.} =
-          if n >= 7: raise newException(ValueError, "foobar: " & $n)
-          else: result = n * 2
-
-        proc asyncFact(n: int): Future[int] {.async.} =
-          if n > 0: result = n * await asyncFact(n-1)
-          else: result = 1
-
-        proc main() {.async.} =
-          block: # then
-            assert asyncFact(3).await == 3*2
-            assert asyncFact(3).then(asyncFact).await == 6*5*4*3*2
-            let x1 = await fn(3)
-            assert x1 == 3 * 2
-            let x2 = await fn(4)
-              .then((a: int) => a.float)
-              .then((a: float) => $a)
-            assert x2 == "8.0"
-
-          block: # then with `onReject` callback
-            var witness = 1
-            await fn(6).then((a: int) => (witness = 2), (r: Error) => (witness = 3))
-            assert witness == 2
-            await fn(7).then((a: int) => (witness = 2), (r: Error) => (witness = 3))
-            assert witness == 3
-
-      template impl(call): untyped =
-        when typeOrVoid(call) is void:
-          var ret: Future[void]
-        else:
-          var ret = default(maybeFuture(typeof(call)))
-        typeof(ret)
-      when T is void:
-        type A = impl(onSuccess())
+
+since (1, 5, 1):
+  #[
+  TODO:
+  * map `Promise.all()`
+  * proc toString*(a: Error): cstring {.importjs: "#.toString()".}
+
+  Note:
+  We probably can't have a `waitFor` in js in browser (single threaded), but maybe it would be possible
+  in in nodejs, see https://nodejs.org/api/child_process.html#child_process_child_process_execsync_command_options
+  and https://stackoverflow.com/questions/61377358/javascript-wait-for-async-call-to-finish-before-returning-from-function-witho
+  ]#
+
+  type Error*  {.importjs: "Error".} = ref object of JsRoot
+    ## https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error
+    message*: cstring
+    name*: cstring
+
+  type OnReject* = proc(reason: Error)
+
+  proc then*[T](future: Future[T], onSuccess: proc, onReject: OnReject = nil): auto =
+    ## See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/then
+    ## Returns a `Future` from the return type of `onSuccess(T.default)`.
+    runnableExamples("-r:off"):
+      from std/sugar import `=>`
+
+      proc fn(n: int): Future[int] {.async.} =
+        if n >= 7: raise newException(ValueError, "foobar: " & $n)
+        else: result = n * 2
+
+      proc asyncFact(n: int): Future[int] {.async.} =
+        if n > 0: result = n * await asyncFact(n-1)
+        else: result = 1
+
+      proc main() {.async.} =
+        block: # then
+          assert asyncFact(3).await == 3*2
+          assert asyncFact(3).then(asyncFact).await == 6*5*4*3*2
+          let x1 = await fn(3)
+          assert x1 == 3 * 2
+          let x2 = await fn(4)
+            .then((a: int) => a.float)
+            .then((a: float) => $a)
+          assert x2 == "8.0"
+
+        block: # then with `onReject` callback
+          var witness = 1
+          await fn(6).then((a: int) => (witness = 2), (r: Error) => (witness = 3))
+          assert witness == 2
+          await fn(7).then((a: int) => (witness = 2), (r: Error) => (witness = 3))
+          assert witness == 3
+
+    template impl(call): untyped =
+      # see D20210421T014713
+      when typeof(block: call) is void:
+        var ret: Future[void]
       else:
-        type A = impl(onSuccess(default(T)))
-      var ret: A
-      asm "`ret` = `future`.then(`onSuccess`, `onReject`)"
-      return ret
-
-    proc catch*[T](future: Future[T], onReject: OnReject): Future[void] =
-      ## See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/catch
-      runnableExamples("-d:nimExperimentalAsyncjsThen"):
-        from std/sugar import `=>`
-        from std/strutils import contains
-
-        proc fn(n: int): Future[int] {.async.} =
-          if n >= 7: raise newException(ValueError, "foobar: " & $n)
-          else: result = n * 2
-
-        proc main() {.async.} =
-          var reason: Error
-          await fn(6).catch((r: Error) => (reason = r)) # note: `()` are needed, `=> reason = r` would not work
-          assert reason == nil
-          await fn(7).catch((r: Error) => (reason = r))
-          assert reason != nil
-          assert  "foobar: 7" in $reason.message
-
-        discard main()
-
-      asm "`result` = `future`.catch(`onReject`)"
+        var ret = default(maybeFuture(typeof(call)))
+      typeof(ret)
+    when T is void:
+      type A = impl(onSuccess())
+    else:
+      type A = impl(onSuccess(default(T)))
+    var ret: A
+    {.emit: "`ret` = `future`.then(`onSuccess`, `onReject`);".}
+    return ret
+
+  proc catch*[T](future: Future[T], onReject: OnReject): Future[void] =
+    ## See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/catch
+    runnableExamples("-r:off"):
+      from std/sugar import `=>`
+      from std/strutils import contains
+
+      proc fn(n: int): Future[int] {.async.} =
+        if n >= 7: raise newException(ValueError, "foobar: " & $n)
+        else: result = n * 2
+
+      proc main() {.async.} =
+        var reason: Error
+        await fn(6).catch((r: Error) => (reason = r)) # note: `()` are needed, `=> reason = r` would not work
+        assert reason == nil
+        await fn(7).catch((r: Error) => (reason = r))
+        assert reason != nil
+        assert  "foobar: 7" in $reason.message
+
+      discard main()
+
+    {.emit: "`result` = `future`.catch(`onReject`);".}