asyncjs.nim 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. #
  2. #
  3. # Nim's Runtime Library
  4. # (c) Copyright 2017 Nim Authors
  5. #
  6. # See the file "copying.txt", included in this
  7. # distribution, for details about the copyright.
  8. ## This module implements types and macros for writing asynchronous code
  9. ## for the JS backend. It provides tools for interaction with JavaScript async API-s
  10. ## and libraries, writing async procedures in Nim and converting callback-based code
  11. ## to promises.
  12. ##
  13. ## A Nim procedure is asynchronous when it includes the ``{.async.}`` pragma. It
  14. ## should always have a ``Future[T]`` return type or not have a return type at all.
  15. ## A ``Future[void]`` return type is assumed by default.
  16. ##
  17. ## This is roughly equivalent to the ``async`` keyword in JavaScript code.
  18. ##
  19. ## .. code-block:: nim
  20. ## proc loadGame(name: string): Future[Game] {.async.} =
  21. ## # code
  22. ##
  23. ## should be equivalent to
  24. ##
  25. ## .. code-block:: javascript
  26. ## async function loadGame(name) {
  27. ## // code
  28. ## }
  29. ##
  30. ## A call to an asynchronous procedure usually needs ``await`` to wait for
  31. ## the completion of the ``Future``.
  32. ##
  33. ## .. code-block:: nim
  34. ## var game = await loadGame(name)
  35. ##
  36. ## Often, you might work with callback-based API-s. You can wrap them with
  37. ## asynchronous procedures using promises and ``newPromise``:
  38. ##
  39. ## .. code-block:: nim
  40. ## proc loadGame(name: string): Future[Game] =
  41. ## var promise = newPromise() do (resolve: proc(response: Game)):
  42. ## cbBasedLoadGame(name) do (game: Game):
  43. ## resolve(game)
  44. ## return promise
  45. ##
  46. ## Forward definitions work properly, you just need to always add the ``{.async.}`` pragma:
  47. ##
  48. ## .. code-block:: nim
  49. ## proc loadGame(name: string): Future[Game] {.async.}
  50. ##
  51. ## JavaScript compatibility
  52. ## ~~~~~~~~~~~~~~~~~~~~~~~~~
  53. ##
  54. ## Nim currently generates `async/await` JavaScript code which is supported in modern
  55. ## EcmaScript and most modern versions of browsers, Node.js and Electron.
  56. ## If you need to use this module with older versions of JavaScript, you can
  57. ## use a tool that backports the resulting JavaScript code, as babel.
  58. import jsffi
  59. import macros
  60. when not defined(js) and not defined(nimdoc) and not defined(nimsuggest):
  61. {.fatal: "Module asyncjs is designed to be used with the JavaScript backend.".}
  62. type
  63. Future*[T] = ref object
  64. future*: T
  65. ## Wraps the return type of an asynchronous procedure.
  66. PromiseJs* {.importcpp: "Promise".} = ref object
  67. ## A JavaScript Promise
  68. proc replaceReturn(node: var NimNode) =
  69. var z = 0
  70. for s in node:
  71. var son = node[z]
  72. let jsResolve = ident("jsResolve")
  73. if son.kind == nnkReturnStmt:
  74. let value = if son[0].kind != nnkEmpty: nnkCall.newTree(jsResolve, son[0]) else: jsResolve
  75. node[z] = nnkReturnStmt.newTree(value)
  76. elif son.kind == nnkAsgn and son[0].kind == nnkIdent and $son[0] == "result":
  77. node[z] = nnkAsgn.newTree(son[0], nnkCall.newTree(jsResolve, son[1]))
  78. else:
  79. replaceReturn(son)
  80. inc z
  81. proc isFutureVoid(node: NimNode): bool =
  82. result = node.kind == nnkBracketExpr and
  83. node[0].kind == nnkIdent and $node[0] == "Future" and
  84. node[1].kind == nnkIdent and $node[1] == "void"
  85. proc generateJsasync(arg: NimNode): NimNode =
  86. if arg.kind notin {nnkProcDef, nnkLambda, nnkMethodDef, nnkDo}:
  87. error("Cannot transform this node kind into an async proc." &
  88. " proc/method definition or lambda node expected.")
  89. result = arg
  90. var isVoid = false
  91. let jsResolve = ident("jsResolve")
  92. if arg.params[0].kind == nnkEmpty:
  93. result.params[0] = nnkBracketExpr.newTree(ident("Future"), ident("void"))
  94. isVoid = true
  95. elif isFutureVoid(arg.params[0]):
  96. isVoid = true
  97. var code = result.body
  98. replaceReturn(code)
  99. result.body = nnkStmtList.newTree()
  100. if len(code) > 0:
  101. var awaitFunction = quote:
  102. proc await[T](f: Future[T]): T {.importcpp: "(await #)", used.}
  103. result.body.add(awaitFunction)
  104. var resolve: NimNode
  105. if isVoid:
  106. resolve = quote:
  107. var `jsResolve` {.importcpp: "undefined".}: Future[void]
  108. else:
  109. resolve = quote:
  110. proc jsResolve[T](a: T): Future[T] {.importcpp: "#", used.}
  111. result.body.add(resolve)
  112. else:
  113. result.body = newEmptyNode()
  114. for child in code:
  115. result.body.add(child)
  116. if len(code) > 0 and isVoid:
  117. var voidFix = quote:
  118. return `jsResolve`
  119. result.body.add(voidFix)
  120. let asyncPragma = quote:
  121. {.codegenDecl: "async function $2($3)".}
  122. result.addPragma(asyncPragma[0])
  123. macro async*(arg: untyped): untyped =
  124. ## Macro which converts normal procedures into
  125. ## javascript-compatible async procedures
  126. if arg.kind == nnkStmtList:
  127. result = newStmtList()
  128. for oneProc in arg:
  129. result.add generateJsasync(oneProc)
  130. else:
  131. result = generateJsasync(arg)
  132. proc newPromise*[T](handler: proc(resolve: proc(response: T))): Future[T] {.importcpp: "(new Promise(#))".}
  133. ## A helper for wrapping callback-based functions
  134. ## into promises and async procedures
  135. proc newPromise*(handler: proc(resolve: proc())): Future[void] {.importcpp: "(new Promise(#))".}
  136. ## A helper for wrapping callback-based functions
  137. ## into promises and async procedures