You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 03:05:59