mirror of
https://github.com/nim-lang/Nim.git
synced 2025-12-29 01:14:41 +00:00
why ? - We already have an emit that does the same thing - The name asm itself is a bit confusing, you might think it's an alias for asm.js or something else. - The asm keyword is used differently on different compiler targets (it makes it inexpressive). - Does anyone (other than some compiler libraries) use asm instead of emit ? If yes, it's a bit strange to use asm somewhere and emit somewhere. By making the asm keyword for js target deprecated, there would be even less use of the asm keyword for js target, reducing the amount of confusion. - New users might accidentally use a non-universal approach via the asm keyword instead of emit, and then when they learn about asm, try to figure out what the differences are. see https://forum.nim-lang.org/t/10821 --------- Co-authored-by: Andreas Rumpf <rumpf_a@web.de>
270 lines
8.7 KiB
Nim
270 lines
8.7 KiB
Nim
#
|
|
#
|
|
# 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.
|
|
##
|
|
## ```nim
|
|
## proc loadGame(name: string): Future[Game] {.async.} =
|
|
## # code
|
|
## ```
|
|
##
|
|
## should be equivalent to
|
|
##
|
|
## ```javascript
|
|
## async function loadGame(name) {
|
|
## // code
|
|
## }
|
|
## ```
|
|
##
|
|
## A call to an asynchronous procedure usually needs `await` to wait for
|
|
## the completion of the `Future`.
|
|
##
|
|
## ```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`:
|
|
##
|
|
## ```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:
|
|
##
|
|
## ```nim
|
|
## proc loadGame(name: string): Future[Game] {.async.}
|
|
## ```
|
|
##
|
|
## 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.
|
|
|
|
# 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
|
|
future*: T
|
|
## Wraps the return type of an asynchronous procedure.
|
|
|
|
PromiseJs* {.importjs: "Promise".} = ref object
|
|
## A JavaScript Promise.
|
|
|
|
|
|
proc replaceReturn(node: var NimNode) =
|
|
var z = 0
|
|
for s in node:
|
|
var son = node[z]
|
|
let jsResolve = ident("jsResolve")
|
|
if son.kind == nnkReturnStmt:
|
|
let value = if son[0].kind != nnkEmpty: nnkCall.newTree(jsResolve, son[0]) else: jsResolve
|
|
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
|
|
|
|
proc isFutureVoid(node: NimNode): bool =
|
|
result = node.kind == nnkBracketExpr and
|
|
node[0].kind == nnkIdent and $node[0] == "Future" and
|
|
node[1].kind == nnkIdent and $node[1] == "void"
|
|
|
|
proc generateJsasync(arg: NimNode): NimNode =
|
|
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")
|
|
if arg.params[0].kind == nnkEmpty:
|
|
result.params[0] = nnkBracketExpr.newTree(ident("Future"), ident("void"))
|
|
isVoid = true
|
|
elif isFutureVoid(arg.params[0]):
|
|
isVoid = true
|
|
|
|
var code = result.body
|
|
replaceReturn(code)
|
|
result.body = nnkStmtList.newTree()
|
|
|
|
if len(code) > 0:
|
|
var awaitFunction = quote:
|
|
proc await[T](f: Future[T]): T {.importjs: "(await #)", used.}
|
|
result.body.add(awaitFunction)
|
|
|
|
var resolve: NimNode
|
|
if isVoid:
|
|
resolve = quote:
|
|
var `jsResolve` {.importjs: "undefined".}: Future[void]
|
|
else:
|
|
resolve = quote:
|
|
proc jsResolve[T](a: T): Future[T] {.importjs: "#", used.}
|
|
proc jsResolve[T](a: Future[T]): Future[T] {.importjs: "#", used.}
|
|
result.body.add(resolve)
|
|
else:
|
|
result.body = newEmptyNode()
|
|
for child in code:
|
|
result.body.add(child)
|
|
|
|
if len(code) > 0 and isVoid:
|
|
var voidFix = quote:
|
|
return `jsResolve`
|
|
result.body.add(voidFix)
|
|
|
|
let asyncPragma = quote:
|
|
{.codegenDecl: "async function $2($3)".}
|
|
|
|
result.addPragma(asyncPragma[0])
|
|
|
|
macro async*(arg: untyped): untyped =
|
|
## Macro which converts normal procedures into
|
|
## javascript-compatible async procedures.
|
|
if arg.kind == nnkStmtList:
|
|
result = newStmtList()
|
|
for oneProc in arg:
|
|
result.add generateJsasync(oneProc)
|
|
else:
|
|
result = generateJsasync(arg)
|
|
|
|
proc newPromise*[T](handler: proc(resolve: proc(response: T))): Future[T] {.importjs: "(new Promise(#))".}
|
|
## A helper for wrapping callback-based functions
|
|
## into promises and async procedures.
|
|
|
|
proc newPromise*(handler: proc(resolve: proc())): Future[void] {.importjs: "(new Promise(#))".}
|
|
## A helper for wrapping callback-based functions
|
|
## into promises and async procedures.
|
|
|
|
template maybeFuture(T): untyped =
|
|
# avoids `Future[Future[T]]`
|
|
when T is Future: T
|
|
else: Future[T]
|
|
|
|
|
|
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:
|
|
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`)".}
|