summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--changelog.md18
-rw-r--r--doc/lib.rst2
-rw-r--r--lib/js/asyncjs.nim110
-rw-r--r--tests/js/tasync.nim26
-rw-r--r--web/website.ini2
5 files changed, 150 insertions, 8 deletions
diff --git a/changelog.md b/changelog.md
index b216b0f17..d519ecfcf 100644
--- a/changelog.md
+++ b/changelog.md
@@ -39,36 +39,37 @@
   what to return if the environment variable does not exist.
 - Bodies of ``for`` loops now get their own scope:
 
-.. code-block:: nim
+```nim
   # now compiles:
   for i in 0..4:
     let i = i + 1
     echo i
+```
 
 - The parsing rules of ``if`` expressions were changed so that multiple
   statements are allowed in the branches. We found few code examples that
   now fail because of this change, but here is one:
 
-.. code-block:: nim
-
+```nim
   t[ti] = if exp_negative: '-' else: '+'; inc(ti)
+```
 
 This now needs to be written as:
 
-.. code-block:: nim
-
+```nim
   t[ti] = (if exp_negative: '-' else: '+'); inc(ti)
+```
 
 - To make Nim even more robust the system iterators ``..`` and ``countup``
   now only accept a single generic type ``T``. This means the following code
   doesn't die with an "out of range" error anymore:
 
-.. code-block:: nim
-
+```nim
   var b = 5.Natural
   var a = -5
   for i in a..b:
     echo i
+```
 
 - ``formatFloat``/``formatBiggestFloat`` now support formatting floats with zero
   precision digits. The previous ``precision = 0`` behavior (default formatting)
@@ -139,3 +140,6 @@ This now needs to be written as:
 - For string formatting / interpolation a new module
   called [strformat](https://nim-lang.org/docs/strformat.html) has been added
   to the stdlib.
+- codegenDecl pragma now works for the JavaScript backend. It returns an empty string for
+  function return type placeholders.
+- Asynchronous programming for the JavaScript backend using the `asyncjs` module.
diff --git a/doc/lib.rst b/doc/lib.rst
index 6eaf6c788..2719472fe 100644
--- a/doc/lib.rst
+++ b/doc/lib.rst
@@ -433,6 +433,8 @@ Modules for JS backend
 * `jsffi <jsffi.html>`_
   Types and macros for easier interaction with JavaScript.
 
+* `asyncjs <asyncjs.html>`_
+  Types and macros for writing asynchronous procedures in JavaScript.
 
 Deprecated modules
 ------------------
diff --git a/lib/js/asyncjs.nim b/lib/js/asyncjs.nim
new file mode 100644
index 000000000..bde3d787f
--- /dev/null
+++ b/lib/js/asyncjs.nim
@@ -0,0 +1,110 @@
+#
+#
+#            Nim's Runtime Library
+#        (c) Copyright 2017 Nim Authors
+#
+#    See the file "copying.txt", included in this
+#    distribution, for details about the copyright.
+
+## This module implements types and macros for writing asynchronous code
+## for the JS backend. It provides tools for interaction with JavaScript async API-s
+## and libraries, writing async procedures in Nim and converting callback-based code
+## to promises.
+##
+## A Nim procedure is asynchronous when it includes the ``{.async.}`` pragma. It
+## should always have a ``Future[T]`` return type or not have a return type at all.
+## A ``Future[void]`` return type is assumed by default.
+##
+## This is roughly equivalent to the ``async`` keyword in JavaScript code.
+##
+## .. code-block:: nim
+##  proc loadGame(name: string): Future[Game] {.async.} =
+##    # code
+##
+## should be equivalent to
+##
+## .. code-block:: 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
+##   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
+##   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 don't need to add the ``{.async.}`` pragma:
+##
+## .. code-block:: nim
+##   proc loadGame(name: string): Future[Game]
+##
+## JavaScript compatibility
+## ~~~~~~~~~~~~~~~~~~~~~~~~~
+##
+## Nim currently generates `async/await` JavaScript code which is supported in modern
+## EcmaScript and most modern versions of browsers, Node.js and Electron.
+## 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.
+
+import jsffi
+import macros
+
+when not defined(js) and not defined(nimdoc) and not defined(nimsuggest):
+  {.fatal: "Module asyncjs is designed to be used with the JavaScript backend.".}
+
+type
+  Future*[T] = ref object
+    future*: T
+  ## Wraps the return type of an asynchronous procedure.
+
+  PromiseJs* {.importcpp: "Promise".} = ref object
+  ## A JavaScript Promise
+
+proc replaceReturn(node: var NimNode) =
+  var z = 0
+  for s in node:
+    var son = node[z]
+    if son.kind == nnkReturnStmt:
+      node[z] = nnkReturnStmt.newTree(nnkCall.newTree(ident("jsResolve"), son[0]))
+    elif son.kind == nnkAsgn and son[0].kind == nnkIdent and $son[0] == "result":
+      node[z] = nnkAsgn.newTree(son[0], nnkCall.newTree(ident("jsResolve"), son[1]))
+    else:
+      replaceReturn(son)
+    inc z
+
+proc generateJsasync(arg: NimNode): NimNode =
+  assert arg.kind == nnkProcDef
+  result = arg
+  if arg.params[0].kind == nnkEmpty:
+    result.params[0] = nnkBracketExpr.newTree(ident("Future"), ident("void"))
+  var code = result.body
+  replaceReturn(code)
+  result.body = nnkStmtList.newTree()
+  var q = quote:
+    proc await[T](f: Future[T]): T {.importcpp: "(await #)".}
+    proc jsResolve[T](a: T): Future[T] {.importcpp: "#".}
+  result.body.add(q)
+  for child in code:
+    result.body.add(child)
+  result.pragma = quote:
+    {.codegenDecl: "async function $2($3)".}
+
+macro async*(arg: untyped): untyped =
+  ## Macro which converts normal procedures into
+  ## javascript-compatible async procedures
+  generateJsasync(arg)
+
+proc newPromise*[T](handler: proc(resolve: proc(response: T))): Future[T] {.importcpp: "(new Promise(#))".}
+  ## A helper for wrapping callback-based functions
+  ## into promises and async procedures
diff --git a/tests/js/tasync.nim b/tests/js/tasync.nim
new file mode 100644
index 000000000..a164827d2
--- /dev/null
+++ b/tests/js/tasync.nim
@@ -0,0 +1,26 @@
+discard """
+  disabled: true
+  output: '''
+0
+x
+'''
+"""
+
+import asyncjs
+
+# demonstrate forward definition
+# for js
+proc y(e: int): Future[string]
+
+proc x(e: int) {.async.} =
+  var s = await y(e)
+  echo s
+
+proc y(e: int): Future[string] {.async.} =
+  echo 0
+  return "x"
+
+
+
+discard x(2)
+
diff --git a/web/website.ini b/web/website.ini
index 5560e67ea..d8deb2d70 100644
--- a/web/website.ini
+++ b/web/website.ini
@@ -67,7 +67,7 @@ srcdoc2: "pure/collections/heapqueue"
 srcdoc2: "pure/fenv;impure/rdstdin;pure/strformat"
 srcdoc2: "pure/segfaults"
 srcdoc2: "pure/basic2d;pure/basic3d;pure/mersenne;pure/coro;pure/httpcore"
-srcdoc2: "pure/bitops;pure/nimtracker;pure/punycode;pure/volatile"
+srcdoc2: "pure/bitops;pure/nimtracker;pure/punycode;pure/volatile;js/asyncjs"
 
 ; Note: everything under 'webdoc' doesn't get listed in the index, so wrappers
 ; should live here