блестящая кнопка загрузки приложения и future_promise: Ошибка в enc2utf8: аргумент не является вектором символов

Я пытаюсь создать обработчик загрузки в блестящем, но использующем 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функция, ожидая вектора символов и получая обещание. Но я не знаю, как это исправить.

Я видел этот , в котором у вопросспрашивающего , похоже, такая же проблема, но решение не было предложено (и их пример не воспроизводился).

Как я могу это исправить?

1 ответ

Обещания работают с R Markdown, но есть и хорошие, и плохие новости.

Хорошие новости

Обещает работать над downloadHandler

Короче говоря, обещания могут использоваться вместо возвращаемого значения: это просто выходное значение, которое предоставляется в какой-то более поздний момент времени. Таким образом, для любого объекта вывода, включая объект, вы можете предоставить обещание, а не значение вывода.

Обещание состоит из future_promise() функция, которая выполняет некоторую медленную операцию (обычно в другом сеансе R) и часть разрешения (которая является частью, которая следует за %...>%оператор), который собирает результаты и обеспечивает разрешение. Комбинация того и другого - promise.

Это немного особенный, поскольку он не получает объект в качестве вывода, но ожидает файл с именем f записывается на диск (и, следовательно, NULLвозвращаемое значение). Исходный код возвратил a, который был препятствием для работы кода (но не причиной ошибки).

Для работы обещаний файл, записанный на диск, должен быть заменен обещанием. Однако в вашем коде последняя строка была close(con), что не является обещанием. Поэтому первым делом необходимо разгрузить запись файла в функцию, которая затем может быть разрешающей частью будущей конструкции.

похоже, не поддерживает обещания для этой части, как упоминалось @Waldi. У меня нет подтверждающей информации по этому поводу.

Плохие новости

Обещания не имеют большого смысла в контексте уценки R

Как объясняется в этой статье , обещания могут использоваться в контексте Shiny и предотвращать блокировку сервера между сеансами . В рамках одного сеанса цикл событий ожидает выполнения всех обещаний перед рендерингом вывода, что фактически приводит к тому же зависанию пользовательского интерфейса, который мы все научились любить. Только когда активен второй сеанс, обещания принесут пользу в производительности.

Полный пример использования 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 заботится обо всех операциях ввода-вывода, включая закрытие соединения

      ```{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)
}
```

Часть пользовательского интерфейса: обратите внимание, что контент теперь возвращает обещание.

      ```{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")
})
```
Другие вопросы по тегам