Skip to content

Provide a method to make a promise synchronous #142

@gadenbuie

Description

@gadenbuie

chromote offers a $wait_for(promise) method that would be useful in many other contexts. In particular, it'd be a useful way to turn an synchronously call an asynchronous tool in ellmer.

I propose we bring the code from chromote/R/synchronize.R into promises as a function called wait_for().

p <- promises::promise({ ...async code... })
p$then(function(value) { ... more async ... })

value <- promises::wait_for(p) # throws if promise is rejected

I believe it will also need an argument to choose which event loop is used, although it would be helpful if @wch or someone with experience there could describe the requirements for that feature.

Research (from o3)

Below is a non-exhaustive “field guide” to the most common names used in mainstream languages for “take an asynchronous/promise-like object and block the current thread until it is fulfilled (or rejected/errored).”

Language Object Type Blocking Call / Construct Remarks
Java CompletableFuture<T> get() or join() get() throws checked exceptions; join() wraps them in CompletionException.
Kotlin Deferred<T> / Job runBlocking { … } wrapper or Deferred.await() inside a runBlocking await() is suspending; only runBlocking makes the current thread wait.
Scala Future[T] Await.result(future, duration) Requires an explicit timeout (Duration.Inf for “forever”).
C# / .NET Task<T> Task.Wait() or Task.Result await does not block—use only inside async methods.
C++11+ std::future<T> get() or wait() get() returns the value (and re-throws); wait() just blocks until ready.
Rust Future futures::executor::block_on(fut) or tokio::runtime::Runtime::block_on(fut) No language-level await that blocks; executors provide it.
Python asyncio.Future / coroutine asyncio.get_event_loop().run_until_complete(coro) Inside coroutines use await, but that’s non-blocking to the loop.
JavaScript / TypeScript (Node) Promise<T> await within an async function is non-blocking; to truly block the thread you’d need third-party helpers (rare).
Go chan T (also *sync.WaitGroup) <-chan receive or wg.Wait() Receiving from a channel / WaitGroup blocks the goroutine until ready.
Swift Task<T> (Structured Concurrency) Task.runAndBlock { … } (Swift 5.10) or Dispatch semaphores Direct blocking is discouraged; bridging APIs exist.
Haskell IO (Async a) wait async In Control.Concurrent.Async.

Observations you can reuse for R naming

  • The word “wait” or “get” dominates when the call returns the promised value synchronously.
  • “join” is also common (Java, C, POSIX threads) but usually connotes waiting for completion, not necessarily retrieving a value.
  • “block_*” or “runBlocking” explicitly advertises that blocking is happening (Kotlin, Rust).
  • Languages with await reserve that keyword for non-blocking suspension inside async code; they supply a separate blocking helper.

Therefore, if your R helper literally suspends the calling R thread until the promise resolves, idiomatic candidates would be:

  • wait(promise) – shortest, mirrors Java/Scala/Haskell
  • get(promise) – emphasizes the returned value like C++/Java
  • join(promise) – if you just care about completion status
  • block_until(promise) or block(promise) – makes the blocking explicit
  • run_blocking(expr) – if you wrap an expression rather than a promise object

Pick whichever aligns best with the existing naming style in your package or the tidyverse conventions (verbs, snake_case).

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