R programming
promise handling
R promises
fulfilled promise
asynchronous programming

Get value from R fullfilled promise

ML System Design practice on Codemia

Design recommenders, ranking systems and training pipelines the way ML interviews actually ask for them, with worked solutions.

Practice ML system design

Introduction

In R, the phrase "promise" can mean two different things: base R lazy-evaluation promises and asynchronous promises from the promises package. If your question is about a fulfilled async promise, the key idea is that you do not synchronously unwrap it; you attach code that runs when the value becomes available.

Understand What a Fulfilled Promise Is

With the promises package, a promise represents a value that will exist later. A fulfilled promise is one that has already completed successfully. Even then, you normally interact with it through then() or the %...>% operator.

The simplest example uses an already fulfilled promise:

r
1library(promises)
2
3p <- promise_resolve(42)
4
5then(p, \(value) {
6  print(value)
7})

This prints 42, but notice the pattern: you do not extract the value by indexing into p or casting it to a regular object. You register a callback that receives the value.

This is the normal asynchronous style in R. It is especially important in Shiny applications, where blocking the main R process defeats the purpose of using promises in the first place.

Use %...>% for Readable Promise Chains

The infix operator %...>% is often easier to read than nested then() calls:

r
1library(promises)
2
3promise_resolve(c(2, 4, 6)) %...>%
4  (\(value) {
5    mean(value)
6  }) %...>%
7  (\(result) {
8    print(result)
9  })

Each step receives the fulfilled value from the previous step. This is the closest equivalent to method chaining in other async ecosystems.

If an error may occur, add %...!% for rejection handling:

r
1library(promises)
2
3promise_reject("something went wrong") %...!%
4  (\(err) {
5    print(err)
6  })

That gives you a full success-and-failure pipeline without blocking.

Async Example with future_promise()

Most real uses involve background work. The future_promise() function from promises integrates with future so the computation can run outside the main R process.

r
1library(promises)
2library(future)
3
4plan(multisession)
5
6p <- future_promise({
7  Sys.sleep(1)
8  21 * 2
9})
10
11p %...>% (\(value) {
12  print(value)
13})

The fulfilled value is still handled by the callback. You do not write:

r
value <- p

and expect value to be 42. That only stores the promise object itself.

Base R Promises Are a Different Thing

Base R also has promise objects behind the scenes for lazy function arguments. Those are usually forced just by using the argument:

r
1show_twice <- function(x) {
2  print(x)
3  print(x)
4}
5
6show_twice({
7  message("evaluated")
8  10
9})

Here x starts as a lazy promise. The first time x is used, R evaluates it and stores the result. That is a different mechanism from an async promise returned by promise_resolve() or future_promise().

This distinction matters because answers about base R lazy evaluation do not solve async promises package questions, and the reverse is also true.

When You Really Need a Synchronous Value

In async code, the clean answer is usually "restructure the code so the next step happens in then()." If you absolutely need a synchronous result, that normally means you should not be using a promise at that point.

For example, if you are using the future package directly, future::value() can block until the result is ready:

r
1library(future)
2
3plan(multisession)
4
5f <- future({
6  Sys.sleep(1)
7  99
8})
9
10result <- value(f)
11print(result)

That works for a future, but it changes the control flow from asynchronous to blocking. In Shiny or other event-driven code, blocking is usually the wrong tradeoff.

Common Pitfalls

The most common mistake is trying to assign the fulfilled value into a normal variable outside the callback and expecting it to be immediately usable. Promise code does not run in that order.

Another issue is mixing up base R promises with the promises package. The word is the same, but the programming model is different.

Some developers also try to force async code into synchronous style because it feels simpler. That usually leads to blocking behavior and poor responsiveness, especially in Shiny apps.

Finally, remember to handle errors. A fulfilled promise is only one path. Real asynchronous code also needs a rejection path when the background work fails.

Summary

  • A fulfilled async promise in R is normally consumed with then() or %...>%.
  • 'promise_resolve() is a simple way to see how fulfilled values flow through callbacks.'
  • 'future_promise() runs work asynchronously, but the result still arrives through a callback.'
  • Base R lazy-evaluation promises are different from async promises in the promises package.
  • If you need a synchronous value, reconsider whether a promise is the right abstraction for that part of the code.

Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

ML System Design practice on Codemia

Design recommenders, ranking systems and training pipelines the way ML interviews actually ask for them, with worked solutions.

Practice ML system design

All Rights Reserved.