6

我正在尝试以闪亮的方式创建下载处理程序,但使用 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")
})

我正在尝试使用 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")
})

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

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

经过一些调试后,我相信会发生这种情况,因为无论运行下载处理程序的什么都在运行该filename函数,期待一个字符向量,并获得一个承诺。但我不确定如何解决这个问题。

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

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

4

1 回答 1

3

Promise 适用于 R Markdown,但也有一些好消息和坏消息。

好消息

承诺适用于 downloadHandler

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

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

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

对于要在 上运行的 Promise downloadHandler,必须将 file-written-to-disk 替换为 Promise。但是,在您的代码中,您的最后一行是close(con),这不是一个承诺。因此,首要任务是将文件写入卸载到函数中,然后该函数可以成为未来构造的解析部分。

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

坏消息

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

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

使用带有 Promise 的 downloadHandler 的完整示例

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

  • 各种未来和解决功能已被隔离
  • 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)
```

为了更清晰,定义两个独立的函数。请注意,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)
}
```

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")
})
```
于 2021-08-29T18:42:00.313 回答