Thanks to visit codestin.com
Credit goes to github.com

Skip to content

feat: Add coro::setup(expr) - Handler support for promise domain setup/teardown parity #68

@schloerke

Description

@schloerke

Goal: Want to have a handler to be executed when a promise is restored within a function, e.g. after await()/yield() calls.

Related current exit calls:

As for the three kinds of exits, I think we could just have:

  • stop() for errors

  • return() for normal exit, and

  • yield()/await() for continuation

Current enter calls:

  • local() - Setup now. Cleanup on function exit: stop(), return().

Missing enter calls:

  • Setup now and on continuation. Cleanup on step exit: stop(), return(), yield(), await().

Proposal: Add coro::setup(expr)

  • It will execute expr before of every coro step (within the defined async function).
  • Items that are added with local_*() within expr are reverted at the end of that step.

This will give us access to both setup/teardown abilities for each step of the coro promise.


Other APIs to considered:

  • on_step(expr) ?
  • with_step(expr)?
  • step_setup(expr)?
  • ... something step related

Other API names rejected

  • entry(expr) - to similar to on.exit() which only happens once, where we want to have this method called many times.
  • reentry(expr) - Implies no initial execution

Reprex problem:

the <- new.env(parent = emptyenv())
the$x <- 0

async_ex <- coro::async(function(x) {
  old_x <- the$x
  the$x <- x
  withr::defer({
    the$x <- old_x
  })

  cli::cli_inform("Before await: {the$x}, {x}")
  await(coro::async_sleep(0.01))

  cli::cli_inform("After await: {the$x}, {x}")
})

async_ex(1)
#> Before await: 1, 1
cli::cli_inform("x: {the$x}")
#> x: 1
async_ex(2)
#> Before await: 2, 2
cli::cli_inform("x: {the$x}")
#> x: 2

while(!later::loop_empty()) later::run_now()
#> After await: 2, 1
#> After await: 0, 2
cli::cli_inform("x: {the$x}")
#> x: 1

Created on 2025-09-22 with reprex v2.1.1

(The example above just happened to not work as the promises were finished out of order. 😞 )


Proposal example:

the <- new.env(parent = emptyenv())
the$x <- 0

async_exec_proposal <- coro::async(function(x) {
  # Run "now" and again after the `await()`
  coro::setup({
    old_x <- the$x
    the$x <- x
    # Run's when "this" coro step is _done_
    withr::defer({
      the$x <- old_x
    })
  })

  cli::cli_inform("Before await: {the$x}, {x}")
  await(coro::async_sleep(0.01))
  cli::cli_inform("After await: {the$x}, {x}")
})

async_exec_proposal(1)
#> Before await: 1, 1
cli::cli_inform("x: {the$x}")
#> x: 0
async_exec_proposal(2)
#> Before await: 2, 2
cli::cli_inform("x: {the$x}")
#> x: 0

while(!later::loop_empty()) later::run_now()
#> After await: 1, 1
#> After await: 2, 2
cli::cli_inform("x: {the$x}")
#> x: 0

Notice how x would be reset to 0 at all times outside of the async func.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions