Plumber: docs for promises on website

Created on 26 Feb 2019  路  9Comments  路  Source: rstudio/plumber

See #248

intermediate medium help wanted medium

Most helpful comment

@schloerke Finally get some time back to this... I've made a Hello World example for this. It works great. Thanks for making this happen!

(Note: Since most of my needs can be solved by running the task in a separate process usingfuture, I havn't yet come up with a good example that uses promises)

The R script under the work directory: plumber-future.R

library(future)
plan(multiprocess)

task_id <- 0

#' @get /query
function(x) {
  task_id <<- task_id + 1
  t0 <- Sys.time()
  message('task ', task_id, ' starts at ', t0)
  future({
    Sys.sleep(x)
    t1 <- Sys.time()
    message('task ', task_id, ' end at ', t1)
    c(t0, t1)
  })
}

Host the API with the following code

library(plumber)
plumber::plumber$new(file = "plumber-future.R", envir = .GlobalEnv)$run(port = 8223)

Fetch the API with the following code

run <- function(x) {
  callr::r_bg(function(x) {
    url <- sprintf("http://127.0.0.1:8223/query?x=%d", x)
    t0 <- Sys.time()
    out <- httr::GET(url)
    t1 <- Sys.time()
    out <- httr::content(out)
    out[[1]] <- as.POSIXct(out[[1]])
    out[[2]] <- as.POSIXct(out[[2]])
    data.frame(
      server_start = out[[1]], server_end = out[[2]], 
      server_cost = as.double.difftime(out[[2]] - out[[1]], 'secs'),
      client_start = t0, client_end = t1, 
      client_cost = as.double.difftime(t1 - t0, 'secs')
    )
  }, args = list(x = x))
}

out1 <- run(3)
out2 <- run(5)
out3 <- run(4)

# wait 5 seconds.... then

dplyr::bind_rows(
  out1$get_result(),
  out2$get_result(),
  out3$get_result()
)

Client

image

Server

image

All 9 comments

WoW, so the asycn feature has been fully implemented?

@shrektan Just merged! 馃槃

All filters, hooks, and routes can return a promise object and the next "stage" within the route's execution will wait until the prior promise value is returned. Each stage of the route handling does not need to worry about the prior result being a promise or not, only to handle the normal (resolved) R value from the prior stage.

Please give it a fair testing. I've done some exhaustive _toy-example_ testing in https://github.com/trestletech/plumber/pull/248/files#diff-39592a994acd05ed47c35ab7e540ff0f . Never hurts to have _real life_ API give it a go.

Great! Can't wait to play it around. 馃憤

@shrektan If you have an idea (or implementation) of a "hello world" or even an intermediate difficulty idea for an example API using future and future::plan(future::multiprocess), I'd appreciate the help!

@schloerke Finally get some time back to this... I've made a Hello World example for this. It works great. Thanks for making this happen!

(Note: Since most of my needs can be solved by running the task in a separate process usingfuture, I havn't yet come up with a good example that uses promises)

The R script under the work directory: plumber-future.R

library(future)
plan(multiprocess)

task_id <- 0

#' @get /query
function(x) {
  task_id <<- task_id + 1
  t0 <- Sys.time()
  message('task ', task_id, ' starts at ', t0)
  future({
    Sys.sleep(x)
    t1 <- Sys.time()
    message('task ', task_id, ' end at ', t1)
    c(t0, t1)
  })
}

Host the API with the following code

library(plumber)
plumber::plumber$new(file = "plumber-future.R", envir = .GlobalEnv)$run(port = 8223)

Fetch the API with the following code

run <- function(x) {
  callr::r_bg(function(x) {
    url <- sprintf("http://127.0.0.1:8223/query?x=%d", x)
    t0 <- Sys.time()
    out <- httr::GET(url)
    t1 <- Sys.time()
    out <- httr::content(out)
    out[[1]] <- as.POSIXct(out[[1]])
    out[[2]] <- as.POSIXct(out[[2]])
    data.frame(
      server_start = out[[1]], server_end = out[[2]], 
      server_cost = as.double.difftime(out[[2]] - out[[1]], 'secs'),
      client_start = t0, client_end = t1, 
      client_cost = as.double.difftime(t1 - t0, 'secs')
    )
  }, args = list(x = x))
}

out1 <- run(3)
out2 <- run(5)
out3 <- run(4)

# wait 5 seconds.... then

dplyr::bind_rows(
  out1$get_result(),
  out2$get_result(),
  out3$get_result()
)

Client

image

Server

image

@shrektan Thank you!

A good use case for promises is handling errors in futures.

A somewhat contrived example, since R handles division by zero just fine:

future::plan(multiprocess)

#' @get /divide
function(a, b) {
  future({
    a <- as.numeric(a)
    b <- as.numeric(b)
    if (is.na(a) || is.na(b)) stop("Invalid request")
    if (b == 0) stop("Cannot divide by 0")
    a / b
  }) %>%
    promises::catch(function(error) list(error = error$message))
}

Or with status envelopes:

success <- function(data) {
  list(status = jsonlite::unbox("success"), data = data)
}

failure <- function(error) {
  list(status = jsonlite::unbox("failed"),
       error = jsonlite::unbox(error$message))
}

#' @get /divide2
#' @raw
function(a, b) {
  future({
    a <- as.numeric(a)
    b <- as.numeric(b)
    if (is.na(a) || is.na(b)) stop("Invalid request")
    if (b == 0) stop("Cannot divide by 0")
    a / b
  }) %>%
    promises::then(onFulfilled = success, onRejected = failure)
}

@antoine-sachet Thanks for sharing this. I think it's a good use case. But it the status code plumber returns is still 200L (success) rather than 400L ro 500L (failure). Do you have a workaround to change the status code that returned?

The endpoint could receive res and set res$status to the relevant code. But setting the status should be done in the envelope function and I don't know whether that's possible.

One solution could be to use a custom serializer that checks the status of the envelope and sets res$status to the adequate code.

But in that case, you might as well handle the envelopes with the serializer directly instead of using promises... Also do error handling using the error handler instead of promises... In hindsight, not sure it was the best use case!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

alexzaytsev-newsroomly picture alexzaytsev-newsroomly  路  6Comments

dmenne picture dmenne  路  4Comments

daxilshah picture daxilshah  路  4Comments

colearendt picture colearendt  路  4Comments

cbuchacher picture cbuchacher  路  4Comments