Как передать прогресс от вызова API официанту

Я хотел бы зафиксировать ход вызова 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)

Я думаю, что невозможно добиться настоящего прогресса. Но можно использовать счетчик, который работает до завершения вызова.

Stéphane Laurent 22.03.2024 14:02

Я посмотрел... На самом деле это должно быть возможно, если вы выполняете вызов API с помощью JavaScript.

Stéphane Laurent 22.03.2024 14:18

Как нам выполнить вызов API с помощью JS, я просто использую пакеты R. Более того, при загрузке блестящего приложения вызов API обеспечивает правильную визуализацию экрана, поэтому я использовал вызывающую программу для создания API в фоновом режиме, чтобы он не удерживал экран загрузки. Затем наткнулся на пакет официанта и подумал, может быть, я мог бы удалить callr и просто использовать страницу официанта и напрямую запустить API. Можете ли вы привести пример того, как выполнить вызов API с помощью JS? У меня выполняется много вызовов API, поэтому, возможно, JS будет быстрее, чем пакет R?

user5249203 22.03.2024 16:51

user> Это вызов API для загрузки файла?

Stéphane Laurent 22.03.2024 17:01

У меня есть 3 вызова API с разными токенами, которые извлекают эти данные и отображают их в блестящей таблице при начальной загрузке приложения.

user5249203 22.03.2024 17:10

user> Так это загрузка, с GET-запросом?

Stéphane Laurent 22.03.2024 17:13

После этого, на основе идентификатора записи, из одного из вызовов API я загружаю CSV-файл для каждой записи и отображаю его на другой блестящей вкладке. Это не выполняется, если не щелкнуть вкладку. Чтобы избежать повторных вызовов API для загрузки одного и того же файла, я сохраняю данные (идентификатор записи и csv df в виде столбцов списка) в виде кадра данных в объекте .RDS. Итак, приложение считывает эту информацию в памяти и anti_join, чтобы загрузить CSV-файл для новых записей. Не уверен, есть ли лучший способ подойти к этому

user5249203 22.03.2024 17:14
data <- REDCapR::redcap_read_oneshot(redcap_uri = url, token = token)$data Я использую пакет REDCapR, как указано выше. Это оболочка по запросу GET.
user5249203 22.03.2024 17:15

user> Я знаю только (думаю), как это сделать для скачивания.

Stéphane Laurent 22.03.2024 17:17
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') Вот как я скачиваю файл, мне просто нужно убедиться, что Пользователь занимается с официантом, чтобы прогресс показывал, что что-то работает сзади
user5249203 22.03.2024 17:21
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
10
67
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Вот как выполнить загрузку в 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 события onprogressfalse; это означает, что это событие не предоставляет общий размер загрузки, и тогда нам придется ввести его вручную.

Это событие постепенно предоставляет загруженный размер. Поскольку это быстро, я замедляюсь с помощью 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) не обновляется должным образом.

user5249203 23.03.2024 21: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)

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

Stéphane Laurent 25.03.2024 11:03

Другие вопросы по теме