Я хотел бы зафиксировать ход вызова API и обновить официанта. Это не отображает прогресс на стороне пользовательского интерфейса. не знаю почему. Если я удалю If, просто получу цикл for, вызовы API завершатся, а затем выполнится цикл for, тогда я увижу, как официант прогрессирует от 0 до 100%. Это потому, что пользовательский интерфейс не работает во время выполнения операции на стороне сервера?
library(shiny)
library(waiter)
library(promises)
library(future)
foo <- function() {
# Simulte a slow download
cap_speed <- httr::config(max_recv_speed_large = 10000)
x <- httr::GET("http://httpbin.org/bytes/102400", httr::progress(con = stderr()), cap_speed)
}
ui <- fluidPage(
useWaiter(),
useHostess(),
waiterShowOnLoad(
color = "#f7fff7",
hostess_loader(
"loader",
preset = "circle",
text_color = "black",
class = "label-center",
center_page = TRUE
)
)
)
server <- function(input, output){
hostess <- Hostess$new("loader")
f <- future({ foo() }) # Create the future
for(i in 1:10){
if (future::resolved(f)) {
hostess$set(100)
waiter_hide()
break
} else {
hostess$set(i * 10)
}
}
}
shinyApp(ui, server)
Я посмотрел... На самом деле это должно быть возможно, если вы выполняете вызов API с помощью JavaScript.
Как нам выполнить вызов API с помощью JS, я просто использую пакеты R. Более того, при загрузке блестящего приложения вызов API обеспечивает правильную визуализацию экрана, поэтому я использовал вызывающую программу для создания API в фоновом режиме, чтобы он не удерживал экран загрузки. Затем наткнулся на пакет официанта и подумал, может быть, я мог бы удалить callr и просто использовать страницу официанта и напрямую запустить API. Можете ли вы привести пример того, как выполнить вызов API с помощью JS? У меня выполняется много вызовов API, поэтому, возможно, JS будет быстрее, чем пакет R?
user> Это вызов API для загрузки файла?
У меня есть 3 вызова API с разными токенами, которые извлекают эти данные и отображают их в блестящей таблице при начальной загрузке приложения.
user> Так это загрузка, с GET-запросом?
После этого, на основе идентификатора записи, из одного из вызовов API я загружаю CSV-файл для каждой записи и отображаю его на другой блестящей вкладке. Это не выполняется, если не щелкнуть вкладку. Чтобы избежать повторных вызовов API для загрузки одного и того же файла, я сохраняю данные (идентификатор записи и csv df в виде столбцов списка) в виде кадра данных в объекте .RDS. Итак, приложение считывает эту информацию в памяти и anti_join, чтобы загрузить CSV-файл для новых записей. Не уверен, есть ли лучший способ подойти к этому
data <- REDCapR::redcap_read_oneshot(redcap_uri = url, token = token)$data Я использую пакет REDCapR, как указано выше. Это оболочка по запросу GET.
user> Я знаю только (думаю), как это сделать для скачивания.
token <- token url <- url formData <- list("token"=token, content='file', action='export', record='31', field='run_csv_file', event='', returnFormat='json' ) response <- httr::POST(url, body = formData, encode = "form") result <- httr::content(response) head(result) f <- file('/tmp/file.raw.txt', 'wb') Вот как я скачиваю файл, мне просто нужно убедиться, что Пользователь занимается с официантом, чтобы прогресс показывал, что что-то работает сзади





Вот как выполнить загрузку в JavaScript с помощью индикатора выполнения.
Файл xhr.js, который нужно поместить в подпапку www приложения:
Shiny.addCustomMessageHandler("xhr", function (x) {
// 1. Create a new XMLHttpRequest object
let xhr = new XMLHttpRequest();
// 2. Configure it: GET-request for the URL
let url = "https://raw.githubusercontent.com/stla/bigjson/main/bigjson.json";
xhr.open("GET", url);
// 3. Send the request over the network
xhr.send();
// 4. This will be called after the response is received
let response; // variable to store the downloaded file
xhr.onload = function () {
if (xhr.status != 200) {
// analyze HTTP status of the response
alert(`Error ${xhr.status}: ${xhr.statusText}`); // e.g. 404: Not Found
} else {
// show the result in the console
console.info(`Done, got ${xhr.response.length} bytes`); // response is the server response
// store the response
// we don't send the response to Shiny here because it would block the progress bar
response = xhr.response;
}
};
let i = 1000; // this is used to slow down the communication with Shiny
let totalSize = 100293197; // the size of the downloaded file
let nsteps = 5; // number of steps for the progress bar
let threshold = 1 / nsteps;
xhr.onprogress = function (event) {
if (event.lengthComputable) { // if available, totalSize is not needed
// because it is given in event.total
let ratio = event.loaded / event.total;
if (ratio >= threshold) {
setTimeout(function () {
Shiny.setInputValue("received", ratio);
if (ratio === 1) { // send response to Shiny
Shiny.setInputValue("download", response);
}
}, i);
threshold += 1 / nsteps;
}
} else { // event.total is not available
let ratio = event.loaded / totalSize;
if (ratio >= threshold) {
setTimeout(function () {
Shiny.setInputValue("received", ratio);
if (ratio === 1) { // send response to Shiny
Shiny.setInputValue("download", response);
}
}, i);
threshold += 1 / nsteps;
}
}
i += 1000;
};
xhr.onerror = function () {
alert("Request failed");
};
});
Здесь я загружаю файл, размещенный на Github (bigjson.json, около 100 МБ). lengthComputable события onprogress — false; это означает, что это событие не предоставляет общий размер загрузки, и тогда нам придется ввести его вручную.
Это событие постепенно предоставляет загруженный размер. Поскольку это быстро, я замедляюсь с помощью setTimeout.
Приложение:
library(shiny)
ui <- fluidPage(
tags$head(tags$script(src = "xhr.js")),
br(),
actionButton("go", "Go"),
br(),
tags$h2("Response will appear here:"),
verbatimTextOutput("response")
)
server <- function(input, output, session) {
# progress bar
progress <- NULL
observeEvent(input$go, {
progress <<- Progress$new(session, min = 0, max = 1)
progress$set(message = "Download in progress")
# trigger the download in JavaScript
session$sendCustomMessage("xhr", TRUE)
})
# input$received contains the ratio of the download in progress
observeEvent(input$received, {
progress$set(value = input$received)
if (input$received == 1) {
progress$close()
}
})
# input$download contains the downloaded file (here a JSON string)
output$response <- renderPrint({
req(input$download)
substr(input$download, 1, 100)
})
}
shinyApp(ui, server)
Спасибо за ответ. Я ищу что-нибудь с пакетом официанта. Обновил свой код, так как нашел вызов API, который могу смоделировать из пакета httr. Это кажется логически правильным, но не уверен, почему hostess$set(i * 10) не обновляется должным образом.
Вот способ использования пакета httr для выполнения запроса и будущего пакета. Однако у меня возникла проблема: когда я закрываю приложение, оно не останавливается (тогда мне приходится перезапускать сеанс R) (см. комментарий).
library(shiny)
library(waiter)
library(future)
plan(multisession) # important
library(httr)
foo <- function() {
# Simulte a slow download
cap_speed <- httr::config(max_recv_speed_large = 10000)
GET("http://httpbin.org/bytes/102400", cap_speed)
}
ui <- fluidPage(
useWaiter(),
useHostess(),
waiterShowOnLoad(
color = "#f7fff7",
hostess_loader(
"loader",
preset = "circle",
text_color = "black",
class = "label-center",
center_page = TRUE
)
)
)
server <- function(input, output){
hostess <- Hostess$new("loader")
f <- future({ foo() }) # Create the future
i <- 0
while(!resolved(f)) {
i <- i + 1
hostess$set(min(99, 10 * i)) # the spinner disappears when 100 is attained
}
hostess$close()
print("done") # do not close the app before
}
shinyApp(ui, server)
Я понимаю, почему приложение не закрылось должным образом: это потому, что я закрыл его до того, как хозяйка закончила.
Я думаю, что невозможно добиться настоящего прогресса. Но можно использовать счетчик, который работает до завершения вызова.