Skip to content

Commit

Permalink
followup #16871 asyncjs.then: allow pipelining procs returning futures (
Browse files Browse the repository at this point in the history
#17189)

* followup #16871 asyncjs.then: allow pipelining procs returning futures
* rename test files where they belong
* fix tests
* tests for then with `onReject` callback
* rename test file containing fail to avoid messing with grep
* address comments
* cleanup
* un-disable 1 test
  • Loading branch information
timotheecour authored Mar 4, 2021
1 parent 5670b84 commit a66637b
Show file tree
Hide file tree
Showing 4 changed files with 80 additions and 21 deletions.
79 changes: 58 additions & 21 deletions lib/js/asyncjs.nim
Original file line number Diff line number Diff line change
Expand Up @@ -57,12 +57,13 @@
## 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.

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
Expand Down Expand Up @@ -157,7 +158,17 @@ proc newPromise*(handler: proc(resolve: proc())): Future[void] {.importcpp: "(ne
## 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:
Expand All @@ -177,44 +188,70 @@ when defined(nimExperimentalAsyncjsThen):

type OnReject* = proc(reason: Error)

proc then*[T, T2](future: Future[T], onSuccess: proc(value: T): T2, onReject: OnReject = nil): Future[T2] =
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
asm "`result` = `future`.then(`onSuccess`, `onReject`)"
## Returns a `Future` from the return type of `onSuccess(T.default)`.
runnableExamples("-d:nimExperimentalAsyncjsThen"):
from std/sugar import `=>`

proc then*[T](future: Future[T], onSuccess: proc(value: T), onReject: OnReject = nil): Future[void] =
## See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/then
asm "`result` = `future`.then(`onSuccess`, `onReject`)"
proc fn(n: int): Future[int] {.async.} =
if n >= 7: raise newException(ValueError, "foobar: " & $n)
else: result = n * 2

proc then*(future: Future[void], onSuccess: proc(), onReject: OnReject = nil): Future[void] =
## See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/then
asm "`result` = `future`.then(`onSuccess`, `onReject`)"
proc asyncFact(n: int): Future[int] {.async.} =
if n > 0: result = n * await asyncFact(n-1)
else: result = 1

proc then*[T2](future: Future[void], onSuccess: proc(): T2, onReject: OnReject = nil): Future[T2] =
## See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/then
asm "`result` = `future`.then(`onSuccess`, `onReject`)"
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())
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:
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.} =
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"

proc main() {.async.} =
var reason: Error
await fn(6).catch((r: Error) => (reason = r))
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`)"
20 changes: 20 additions & 0 deletions tests/js/tasync.nim → tests/js/tasyncjs.nim
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,14 @@ proc fn(n: int): Future[int] {.async.} =
else:
return 10

proc asyncFact(n: int): Future[int] {.async.} =
if n > 0: result = n * await asyncFact(n-1)
else: result = 1

proc asyncIdentity(n: int): Future[int] {.async.} =
if n > 0: result = 1 + await asyncIdentity(n-1)
else: result = 0

proc main() {.async.} =
block: # then
let x = await fn(4)
Expand All @@ -63,6 +71,18 @@ proc main() {.async.} =
let x2 = await fn(4).then((a: int) => (discard)).then(() => 13)
doAssert x2 == 13

let x4 = await asyncFact(3).then(asyncIdentity).then(asyncIdentity).then((a:int) => a * 7).then(asyncIdentity)
doAssert x4 == 3 * 2 * 7

block: # bug #17177
proc asyncIdentityNested(n: int): Future[int] {.async.} = return n
let x5 = await asyncFact(3).then(asyncIdentityNested)
doAssert x5 == 3 * 2

when false: # xxx pending bug #17254
let x6 = await asyncFact(3).then((a:int) {.async.} => a * 11)
doAssert x6 == 3 * 2 * 11

block: # catch
var reason: Error
await fn(6).then((a: int) => (witness.add $a)).catch((r: Error) => (reason = r))
Expand Down
File renamed without changes.
2 changes: 2 additions & 0 deletions tests/js/tasync_pragma.nim → tests/js/tasyncjs_pragma.nim
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ t
'''
"""

# xxx merge into tasyncjs.nim

import asyncjs, macros

macro f*(a: untyped): untyped =
Expand Down

0 comments on commit a66637b

Please sign in to comment.