如何在Shiny中用ExtendedTask实现一键式无阻塞下载?
一键实现Shiny异步无阻塞数据下载的方案探索
背景
我尝试在Shiny应用中结合无阻塞功能与下载按钮,提升会话内并发能力,最初尝试失败后,通过异步编程实现了基础功能,之后借助ExtendedTask实现了两步式下载,但希望优化为一键完成下载,目前修改后的代码在Edge浏览器+Shiny 1.10.0环境下无法正常工作,下载内容为HTML页面而非目标文件。
1. 可正常运行的异步下载基础示例
library(shiny) library(future) library(promises) library(writexl) library(data.table) ui <- fluidPage( titlePanel("Cars Data"), uiOutput("download_format"), downloadButton("download_data", "Download") ) server <- function(input, output) { output$download_format <- renderUI({ radioButtons("download_format", "Download Format:", choices = c("CSV", "Excel"), inline = T) }) output$download_data <- downloadHandler( filename = function() { if(input$download_format == "Excel") { paste("Data-", Sys.time(), ".xlsx", sep="") } else { paste("Data-", Sys.time(), ".csv", sep="") } }, content = function(file) { download_format <- input$download_format future_promise({ df <- mtcars Sys.sleep(10) if(download_format == "Excel") { writexl::write_xlsx(df, file) } else { data.table::fwrite(df, file) } }) } ) } shinyApp(ui = ui, server = server)
2. 两步式的ExtendedTask下载示例(需先点击Export再点击Download)
感谢@Mikko和@smartse的帮助,实现了数据准备完成后启用下载按钮的功能,但需要两步操作:
library(shiny) library(bslib) library(future) library(promises) future::plan(multisession) ui <- fluidPage( shinyjs::useShinyjs(), titlePanel("Cars Data"), textOutput("time"), bslib::input_task_button("export", "Export", icon = icon("file-export")), downloadButton("download", "Download") |> shinyjs::hidden(), ) server <- function(input, output, session) { # 实时更新时间证明UI未被阻塞 output$time <- renderText({ invalidateLater(1000) format(Sys.time()) }) # 异步准备下载文件的任务 export_task <- ExtendedTask$new(function(file) { promises::future_promise({ data <- mtcars Sys.sleep(2) write.csv(data, file) file }) }) |> bslib::bind_task_button("export") # 为当前会话创建临时文件,会话结束后自动删除 download_content_path <- tempfile("download_content") session$onEnded(function() unlink(download_content_path)) observeEvent(input$export, export_task$invoke(download_content_path)) # 文件准备完成后显示下载按钮 observe({ if (export_task$status() == "success") { showNotification("Your download is ready.") shinyjs::show("download") } else { shinyjs::hide("download") } }) # 处理下载请求 output$download <- downloadHandler( filename = function() { paste0("Data-", Sys.time(), ".csv") }, content = function(file) { file.copy(export_task$result(), file) } ) } shinyApp(ui = ui, server = server)
3. 尝试一键下载但失败的示例
修改代码后尝试自动触发下载按钮点击,但在Edge浏览器+Shiny 1.10.0中下载内容为HTML页面:
library(shiny) library(bslib) library(future) library(promises) future::plan(multisession) ui <- fluidPage( shinyjs::useShinyjs(), titlePanel("Cars Data"), textOutput("time"), bslib::input_task_button("export", "Export", icon = icon("file-export")), downloadButton("download", "Download") |> shinyjs::hidden(), ) server <- function(input, output, session) { # 实时更新时间证明UI未被阻塞 output$time <- renderText({ invalidateLater(1000) format(Sys.time()) }) # 异步准备下载文件的任务 export_task <- ExtendedTask$new(function(file) { promises::future_promise({ data <- mtcars Sys.sleep(2) write.csv(data, file) file }) }) |> bslib::bind_task_button("export") # 为当前会话创建临时文件,会话结束后自动删除 download_content_path <- tempfile("download_content") session$onEnded(function() unlink(download_content_path)) observeEvent(input$export, export_task$invoke(download_content_path)) # 文件准备完成后自动触发下载按钮点击 observe({ if (export_task$status() == "success") { showNotification("Your download is ready.") shinyjs::click("download") # 以下两种JS触发方式也尝试过,同样无效 # shinyjs::runjs("$('#download')[0].click();") # shinyjs::runjs("document.getElementById('download').click();") } }) # 处理下载请求 output$download <- downloadHandler( filename = function() { paste0("Data-", Sys.time(), ".csv") }, content = function(file) { file.copy(export_task$result(), file) } ) } shinyApp(ui = ui, server = server)
问题
是否有办法实现一键点击即可完成数据下载,无需两步操作?
内容的提问来源于stack exchange,提问作者Deep
相关产品推荐
相关产品推荐

