import pkg/chronos import pkg/questionable import pkg/questionable/results import pkg/upraises # Similar to JavaScript's Promise API, `.then` and `.catch` can be used to # handle results and errors of async `Futures` within a synchronous closure. # They can be used as an alternative to `asyncSpawn` which does not return a # value and will raise a `FutureDefect` if there are unhandled errors # encountered. Both `.then` and `.catch` act as callbacks that do not block the # synchronous closure's flow. # `.then` is called when the `Future` is successfully completed and can be # chained as many times as desired, calling each `.then` callback in order. When # the `Future` returns `Result[T, ref CatchableError]` (or `?!T`), the value # called in the `.then` callback will be unpacked from the `Result` as a # convenience. In other words, for `Future[?!T]`, the `.then` callback will take # a single parameter `T`. See `tests/utils/testthen.nim` for more examples. To # allow for chaining, `.then` returns its future. If the future is already # complete, the `.then` callback will be executed immediately. # `.catch` is called when the `Future` fails. In the case when the `Future` # returns a `Result[T, ref CatchableError` (or `?!T`), `.catch` will be called # if the `Result` contains an error. If the `Future` is already failed (or # `Future[?!T]` contains an error), the `.catch` callback will be executed # immediately. # `.cancelled` is called when the `Future` is cancelled. If the `Future` is # already cancelled, the `.cancelled` callback will be executed immediately. # More info on JavaScript's Promise API can be found at: # https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise runnableExamples: proc asyncProc(): Future[int] {.async.} = await sleepAsync(1.millis) return 1 asyncProc() .then(proc(i: int) = echo "returned ", i) .catch(proc(e: ref CatchableError) = doAssert false, "will not be triggered") # outputs "returned 1" proc asyncProcWithError(): Future[int] {.async.} = await sleepAsync(1.millis) raise newException(ValueError, "some error") asyncProcWithError() .then(proc(i: int) = doAssert false, "will not be triggered") .catch(proc(e: ref CatchableError) = echo "errored: ", e.msg) # outputs "errored: some error" type OnSuccess*[T] = proc(val: T) {.gcsafe, upraises: [].} OnError* = proc(err: ref CatchableError) {.gcsafe, upraises: [].} OnCancelled* = proc() {.gcsafe, upraises: [].} proc ignoreError(err: ref CatchableError) = discard proc ignoreCancelled() = discard template handleFinished(future: FutureBase, onError: OnError, onCancelled: OnCancelled) = if not future.finished: return if future.cancelled: onCancelled() return if future.failed: onError(future.error) return proc then*(future: Future[void], onSuccess: OnSuccess[void]): Future[void] = proc cb(udata: pointer) = future.handleFinished(ignoreError, ignoreCancelled) onSuccess() proc cancellation(udata: pointer) = if not future.finished(): future.removeCallback(cb) future.addCallback(cb) future.cancelCallback = cancellation return future proc then*[T](future: Future[T], onSuccess: OnSuccess[T]): Future[T] = proc cb(udata: pointer) = future.handleFinished(ignoreError, ignoreCancelled) if val =? future.read.catch: onSuccess(val) proc cancellation(udata: pointer) = if not future.finished(): future.removeCallback(cb) future.addCallback(cb) future.cancelCallback = cancellation return future proc then*[T](future: Future[?!T], onSuccess: OnSuccess[T]): Future[?!T] = proc cb(udata: pointer) = future.handleFinished(ignoreError, ignoreCancelled) try: if val =? future.read: onSuccess(val) except CatchableError as e: ignoreError(e) proc cancellation(udata: pointer) = if not future.finished(): future.removeCallback(cb) future.addCallback(cb) future.cancelCallback = cancellation return future proc then*(future: Future[?!void], onSuccess: OnSuccess[void]): Future[?!void] = proc cb(udata: pointer) = future.handleFinished(ignoreError, ignoreCancelled) try: if future.read.isOk: onSuccess() except CatchableError as e: ignoreError(e) return proc cancellation(udata: pointer) = if not future.finished(): future.removeCallback(cb) future.addCallback(cb) future.cancelCallback = cancellation return future proc catch*[T](future: Future[T], onError: OnError) = if future.isNil: return proc cb(udata: pointer) = future.handleFinished(onError, ignoreCancelled) proc cancellation(udata: pointer) = if not future.finished(): future.removeCallback(cb) future.addCallback(cb) future.cancelCallback = cancellation proc catch*[T](future: Future[?!T], onError: OnError) = if future.isNil: return proc cb(udata: pointer) = future.handleFinished(onError, ignoreCancelled) try: if err =? future.read.errorOption: onError(err) except CatchableError as e: onError(e) proc cancellation(udata: pointer) = if not future.finished(): future.removeCallback(cb) future.addCallback(cb) future.cancelCallback = cancellation proc cancelled*[T](future: Future[T], onCancelled: OnCancelled): Future[T] = proc cb(udata: pointer) = future.handleFinished(ignoreError, onCancelled) proc cancellation(udata: pointer) = if not future.finished(): future.removeCallback(cb) onCancelled() future.addCallback(cb) future.cancelCallback = cancellation return future proc cancelled*[T](future: Future[?!T], onCancelled: OnCancelled): Future[?!T] = proc cb(udata: pointer) = future.handleFinished(ignoreError, onCancelled) proc cancellation(udata: pointer) = if not future.finished(): future.removeCallback(cb) onCancelled() future.addCallback(cb) future.cancelCallback = cancellation return future