闪亮的应用程序下载按钮和 future_promise:enc2utf8 中的错误:参数不是字符向量

ric*_*rey 5 r shiny r-future r-promises

我正在尝试创建一个闪亮的下载处理程序,但使用 future_promise() 因为写入文件可能需要一些时间。这是我想做的一个工作示例,但不使用异步框架:

一个有效的 .Rmd 闪亮应用程序:当您单击按钮时,它会将 10 个随机偏差写入文件并提供下载。我添加了 5 秒的延迟。

---
title: "download, no futures"
runtime: shiny
output: html_document
---

```{r setup, include=FALSE}
library(dplyr)
knitr::opts_chunk$set(echo = FALSE)
```

This version works.

```{r}
renderUI({
  
  button_reactive <- reactive({
    y = rnorm(10)
    Sys.sleep(5)
    tf = tempfile(fileext = ".txt")
    cat(c(y,'\n'), sep='\n', file = tf)
    d = readBin(con = tf, what = "raw", n = file.size(tf))
    return(list(fn = basename(tf), d = d))
  })
  
  output$button <- downloadHandler(
      filename = function() {
        button_reactive() %>%
          `[[`('fn')
      },
      content = function(f) {
        d = button_reactive() %>%
          `[[`('d')
        con = file(description = f, open = "wb")
        writeBin(object = d, con = con)
        close(con)
      }
    )
  
  shiny::downloadButton(outputId = "button", label="Download")
})
Run Code Online (Sandbox Code Playgroud)

我正在尝试使用 future_promise 在异步框架中实现这一点。这是 {future}/{promises} 版本:

---
title: "download futures"
runtime: shiny
output: html_document
---

```{r setup, include=FALSE}
library(future)
library(promises)
plan(multisession)
library(dplyr)
knitr::opts_chunk$set(echo = FALSE)
```

This version yields this error on download attempt, reported in the R console:

```
Warning: Error in enc2utf8: argument is not a character vector
  [No stack trace available]
```

```{r}
renderUI({
  
  button_reactive <- reactive({
    future_promise({
      y = rnorm(10)
      Sys.sleep(5)
      tf = tempfile(fileext = ".txt")
      cat(c(y,'\n'), sep='\n', file = tf)
      d = readBin(con = tf, what = "raw", n = file.size(tf))
    return(list(fn = basename(tf), d = d))
    }, seed = TRUE)
  })
  
  output$button <- downloadHandler(
      filename = function() {
        button_reactive() %...>%
          `[[`('fn')
      },
      content = function(f) {
        con = file(description = f, open = "wb")
        d = button_reactive() %...>%
          `[[`('d') %...>%
          writeBin(object = ., con = con)
        close(con)
      }
    )
  
  shiny::downloadButton(outputId = "button", label="Download")
})
Run Code Online (Sandbox Code Playgroud)

当我在 Firefox 中单击按钮时,我没有得到任何文件,并且在 R 控制台中显示:

Warning: Error in enc2utf8: argument is not a character vector
  [No stack trace available]
Run Code Online (Sandbox Code Playgroud)

经过一些调试,我相信这是因为运行下载处理程序的任何东西都在运行该filename函数,期待一个字符向量,并得到一个承诺。但我不确定如何解决这个问题。

我看到了这个问题,其中提问者似乎有同样的问题,但没有提供解决方案(他们的例子不可重现)。

我怎样才能解决这个问题?

All*_*ere 3

Promise 可以与 R Markdown 一起使用,但有一些好消息和坏消息。

好消息

Promise 在 downloadHandler 上工作

简而言之,Promise 可以用来代替返回值:它只是在稍后某个时间点提供的输出值。因此对于任何输出对象,包括downloadHandler,您可以提供承诺而不是输出值。

Promise 由一个future_promise()函数组成,该函数执行一些运行缓慢的操作(通常在不同的 R 会话中)和一个解析部分(跟随运算符的部分%...>%),该部分获取结果并提供解析。两者的结合就是promise.

downloadHandler有点特殊,因为它不接收对象作为输出,而是期望将同名文件f写入磁盘(因此NULL返回值)。您的原始代码返回了 a close(con),这是使代码正常工作的障碍(但不是错误的原因)。

为了让 Promise 在 上工作downloadHandler,写入磁盘的文件必须被 Promise 替换。然而,在您的代码中,您的最后一行是close(con),这不是一个承诺。因此,首要任务是将文件写入卸载到函数,然后该函数可以成为未来构造的解析部分。

downloadHandler正如@Waldi 所提到的,似乎不支持该filename部分的承诺。我没有这方面的支持信息。

坏消息

Promise 在 R markdown 上下文中没有多大意义

正如本文中所解释的,Promise 可以在 Shiny 上下文中使用,并防止跨会话锁定服务器。在单个会话中,事件循环会等待所有承诺在渲染输出之前解决,这会导致我们都喜欢的同样卡住的 UI。只有当第二个会话处于活动状态时,才会承诺产生任何性能优势。

使用 downloadHandler 和 Promise 的完整示例

下面的代码是上面代码的改编版,有三个小区别:

  • 各种 future 和 resolve 函数已被隔离
  • downloadHandler filename参数现在是静态的
  • downloadHandler content论证提供了充分的承诺

保留序言

---
title: "download futures"
runtime: shiny
output: html_document
---

```{r setup, include=FALSE}
library(future)
library(promises)
plan(multisession)
library(dplyr)
knitr::opts_chunk$set(echo = FALSE)
```
Run Code Online (Sandbox Code Playgroud)

为了更加清晰,定义两个独立的函数。请注意,writeFile这里负责处理所有 I/O,包括关闭连接

```{r}
createFile = function(){
  y = rnorm(10)
  Sys.sleep(1)
  tf = tempfile(fileext = ".txt")
  cat(c(y,'\n'), sep='\n', file = tf)
  d = readBin(con = tf, what = "raw", n = file.size(tf))
  return(list(fn = basename(tf), d = d))
  }

writeFile = function (fut, f){
    x = fut[['d']]
    con = file(description = f, open = "wb")
    writeBin(object = x, con = con) 
    close(con)
}
```
Run Code Online (Sandbox Code Playgroud)

UI 部分:请注意,内容现在返回一个承诺。

```{r}
renderUI({
  
  testPromise = reactive({
    future_promise({createFile()}, seed=T) %...>% (function (x) (x))()
  })
  
  fileName = reactive({
    testPromise() %...>% '[['('fn')
  })
  
  output$button <- downloadHandler(
      filename = function() {
        'test.txt'
        # This doesn't work - filename apparently doesn't support promises
        # fileName() 
        
      },
      content = function(f) {
        # Content needs to receive promise as return value, so including resolution
        testPromise() %...>% writeFile(., f)
      }
    )
  
  shiny::downloadButton(outputId = "button", label="Download")
})
```
Run Code Online (Sandbox Code Playgroud)