Gestion des erreurs Shiny : tryCatch, validate(need), req et valeurs de repli sûres
Empêchez une seule mauvaise entrée de faire planter l’application — interceptez les échecs avec tryCatch() et renvoyez une valeur de repli sûre, affichez un message convivial avec validate(need()), passez en silence avec req(), et repliez-vous sur des valeurs sûres par défaut.
Une erreur R brute dans une application Shiny clignote en rouge dans la sortie et s’arrête là — mauvaise UX et impasse pour l’utilisateur. Cette leçon est la boîte à outils de l’échec en douceur : tryCatch() pour intercepter un vrai échec à l’intérieur d’un reactive ou d’un render et renvoyer une valeur sûre par défaut, validate(need()) pour transformer un état pas-encore-prêt en un message convivial, req() pour attendre silencieusement les entrées, des valeurs de repli quand quelque chose tourne mal, et warning() versus stop(). Plus une règle claire pour choisir entre req(), validate() et tryCatch().
- Une erreur brute est une mauvaise UX. Quand du code dans un
render*()ou unreactive()échoue, Shiny affiche l’erreur sous forme de texte rouge dans la sortie et l’application cesse de faire un travail utile. Interceptez-la et récupérez à la place. tryCatch()intercepte un vrai échec et renvoie une valeur de repli. Enveloppez le code risqué (read.csv(), un ajustement, une analyse syntaxique) danstryCatch({ ... }, error = function(e) <safe default>)pour qu’une seule entrée défaillante ne fasse pas planter la sortie.validate(need())transforme un état pas-encore-prêt en un message. Quand l’application n’est tout simplement pas prête (« Téléversez un fichier pour commencer »), affichez un avis convivial plutôt qu’une erreur rouge.req()passe en silence jusqu’à ce que les entrées soient prêtes. Utilisez-le pour le cas vide-au-démarrage où l’utilisateur n’a simplement pas encore choisi — aucun message nécessaire.- Choisissez selon l’intention :
req()pour attendre en silence ·validate(need())pour afficher un message ·tryCatch()pour intercepter un vrai échec et récupérer avec une valeur sûre par défaut.
Introduction
Tout ce qui précède supposait le scénario idéal : l’entrée est valide, le fichier s’analyse, le modèle s’ajuste. Les vraies applications rencontrent de mauvaises données, des fichiers manquants et des entrées qui produisent du n’importe quoi — et quand le code échoue, Shiny fait la pire chose possible pour un utilisateur : il affiche l’erreur R brute sous forme de texte rouge en plein dans la sortie, puis s’arrête.
Error in read.csv(input$file$datapath): cannot open the connection ne signifie rien pour la personne qui utilise votre application, et la laisse coincée. Une application prête pour la production échoue en douceur à la place : elle intercepte l’échec, affiche quelque chose de sensé, et garde le reste de l’application fonctionnel. Cette leçon est la boîte à outils pour cela — tryCatch(), validate(need()), req(), et les valeurs sûres par défaut — et une règle claire sur laquelle choisir. Elle s’appuie sur la programmation réactive et complète la série sur le server.
Le code de cette page n’est pas exécuté ici — une application Shiny a besoin d’une session R active, elle ne peut donc pas tourner dans une page web statique. Copiez n’importe quel bloc dans un fichier app.R et exécutez-le en local avec shiny::runApp() (ou cliquez sur Run App dans RStudio). Chaque extrait se dépose dans la fonction server d’une application.
Où les erreurs surgissent dans Shiny
Quand une expression à l’intérieur d’un render*() ou d’un reactive() lève une erreur, Shiny l’intercepte à la frontière et affiche le message d’erreur sous forme de texte rouge dans la zone de cette sortie. La sortie qui a échoué affiche l’erreur ; le reste de l’application continue de tourner. C’est mieux qu’un plantage brutal, mais c’est tout de même une mauvaise UX — l’utilisateur voit un message technique sur lequel il ne peut pas agir, là où il attendait un graphique ou un tableau.
output$summary <- renderPrint({
d <- read.csv(input$file$datapath) # fails if no file, or a bad path
summary(d) # never reached — the output shows the raw error
})Le but de tout ce qui suit est de remplacer cette erreur brute par quelque chose d’utile : un message convivial, une attente silencieuse, ou une valeur de repli sûre. Choisissez l’outil selon le pourquoi le code ne peut pas s’exécuter.
Intercepter un vrai échec avec tryCatch()
Quand du code peut véritablement échouer — lire un fichier qui n’est pas un CSV valide, ajuster un modèle qui ne converge pas, analyser du texte saisi par l’utilisateur — enveloppez-le dans tryCatch(). Il exécute l’expression risquée ; si celle-ci lève une erreur, le gestionnaire error = s’exécute à la place et vous renvoyez une valeur sûre par défaut pour que la sortie reste sensée.
Utilisez-le quand : une opération peut échouer à l’exécution et que vous voulez récupérer plutôt qu’afficher une erreur rouge.
output$summary <- renderPrint({
tryCatch({
d <- read.csv(input$file$datapath)
summary(d)
}, error = function(e) {
"Could not read the file. Please upload a valid CSV."
})
})Si read.csv() réussit, le summary() est renvoyé comme d’habitude. S’il lève une erreur, le gestionnaire renvoie un simple message à la place — pas d’erreur rouge, pas de sortie morte. Le gestionnaire reçoit l’objet d’erreur e, vous pouvez donc inspecter e$message si vous le souhaitez, mais pour les utilisateurs un message fixe et net se lit mieux que celui, brut, de R.
Le même motif protège un graphique — repliez-vous sur un graphique vide accompagné d’une note plutôt qu’un plantage :
output$plot <- renderPlot({
tryCatch({
d <- read.csv(input$file$datapath)
hist(d[[input$column]])
}, error = function(e) {
plot.new()
text(0.5, 0.5, "Could not draw the plot — check the file and column.")
})
})tryCatch() sert aux échecs dont vous pouvez récupérer. Il intercepte l’erreur, exécute votre valeur de repli, et l’application continue.
Afficher un message convivial avec validate(need())
Parfois le code n’a pas échoué — l’application n’est tout simplement pas encore prête, et vous voulez dire à l’utilisateur quoi faire : « Téléversez un fichier pour commencer », « Choisissez au moins un groupe ». validate(need(cond, "message")) vérifie une condition et, si elle échoue, affiche votre message à la place de la sortie — une instruction nette, pas une erreur rouge.
Utilisez-le quand : l’état vide ou invalide mérite une explication sur laquelle l’utilisateur peut agir.
output$summary <- renderPrint({
validate(
need(input$file, "Upload a CSV file to begin.")
)
d <- read.csv(input$file$datapath)
summary(d)
})need(cond, message) passe (renvoie NULL) quand cond est valide (truthy), de sorte que le render se poursuit ; quand cond est invalide (falsy), il renvoie le message et validate() arrête le render et l’affiche. C’est le mécanisme de protection couvert dans la logique conditionnelle — ici, c’est un outil parmi d’autres dans la boîte à outils de la gestion des erreurs : faites-y appel chaque fois qu’un état pas-encore-prêt devrait s’expliquer de lui-même plutôt que rester vide ou planter.
Passer en silence jusqu’à ce que les entrées soient prêtes avec req()
Souvent il n’y a rien à dire — l’utilisateur n’a simplement pas encore choisi, et la sortie devrait juste attendre tranquillement. req() vérifie que ses arguments sont valides (truthy) et, si ce n’est pas le cas, arrête silencieusement le reactive de sorte que la sortie reste vide au lieu de produire une erreur sur un démarrage à vide.
Utilisez-le quand : une entrée est vide au démarrage et qu’une sortie vide est la bonne chose — aucun message nécessaire.
output$summary <- renderPrint({
req(input$file) # wait silently until a file is uploaded
d <- read.csv(input$file$datapath)
summary(d)
})Tant que input$file n’a pas de valeur, le render ne s’exécute pas et la sortie reste vide — pas d’erreur rouge sur le démarrage à vide. Placez req() en tête, en listant toutes les entrées dont le corps a besoin. Lui et validate(need()) sont couverts en profondeur dans la logique conditionnelle ; en bref req() pour le silence, validate() pour un message.
Se replier sur une valeur sûre par défaut
Le fil conducteur de tryCatch() et validate() est le même : quand vous ne pouvez pas produire le vrai résultat, renvoyez quelque chose de sûr plutôt que de laisser le code exploser. Une valeur sûre par défaut, c’est simplement une valeur que la sortie peut afficher sans se plaindre — NULL, un data frame vide, un graphique de remplacement, une chaîne « pas de données ».
Renvoyez NULL (ou un data frame vide) pour que les sorties en aval restent muettes :
clean_data <- reactive({
tryCatch(
read.csv(input$file$datapath),
error = function(e) NULL # safe default: nothing, not a crash
)
})
output$table <- renderTable({
d <- clean_data()
req(d) # if the read failed, wait quietly
d
})Dessinez un remplacement vide plutôt que de produire une erreur quand un graphique n’a pas de données à afficher :
output$plot <- renderPlot({
d <- clean_data()
if (is.null(d) || nrow(d) == 0) {
plot.new()
text(0.5, 0.5, "No data to display yet.")
return(invisible())
}
hist(d$value)
})Une valeur sûre par défaut garde la forme de l’application intacte — une sortie qui affiche « pas de données pour l’instant » vaut bien mieux qu’une qui affiche une trace d’appels rouge. Choisissez la valeur de repli qui se lit naturellement pour cette sortie : un graphique de remplacement, un tableau vide, un court message.
warning() vs stop(), et où chacun s’affiche
R a deux façons pour le code de signaler un problème, et elles se comportent différemment dans Shiny :
stop()lève une erreur : elle interrompt l’expression. Dans unrender*(), cela devient le texte d’erreur rouge dans la sortie.tryCatch(..., error = ...)l’intercepte.warning()lève un avertissement : le code continue de tourner. Shiny n’affiche pas les avertissements dans la sortie — ils vont à la console R / au journal du serveur, donc l’utilisateur ne les voit jamais, mais ils peuvent masquer de vrais problèmes pendant le développement.
tryCatch() peut gérer les deux, plus un bloc finally = qui s’exécute toujours (succès ou échec) — pratique pour le nettoyage :
result <- tryCatch({
risky_step()
}, error = function(e) {
NULL # recover from a real failure
}, warning = function(w) {
NULL # treat a warning as a failure here too
}, finally = {
message("attempted risky_step") # always runs — goes to the log
})Utilisez le gestionnaire warning = quand un avertissement signifie que le résultat n’est pas fiable et que vous préféreriez vous replier. Utilisez finally = pour le nettoyage qui doit avoir lieu dans tous les cas (fermer une connexion, journaliser une tentative). La plupart du temps, vous n’avez besoin que de error =.
Choisir : req() vs validate() vs tryCatch()
Ces trois outils répondent à trois questions différentes — l’entrée est-elle prête ?, dois-je le dire à l’utilisateur ?, et l’opération a-t-elle échoué ? Choisissez selon l’intention :
| La situation | Faites appel à | L’utilisateur voit |
|---|---|---|
| L’entrée est vide au démarrage ; attendez simplement | req(input$x) |
une sortie vide, silencieusement |
| Pas prête, et vous voulez guider l’utilisateur | validate(need(input$x, "message")) |
votre message convivial |
| Le code peut véritablement échouer (lecture, analyse, ajustement) | tryCatch({ ... }, error = function(e) <fallback>) |
la valeur de repli sûre, pas une erreur rouge |
Ils se composent. Une même sortie protège souvent les entrées avec req(), oriente avec validate(need()), et enveloppe le travail risqué dans tryCatch() :
output$plot <- renderPlot({
req(input$file) # silent: wait for the upload
validate(need(input$column, "Choose a column.")) # message: the user must pick
tryCatch({
d <- read.csv(input$file$datapath)
hist(d[[input$column]])
}, error = function(e) {
plot.new(); text(0.5, 0.5, "Could not draw the plot.")
})
})Règle générale : req() pour attendre, validate() pour expliquer, tryCatch() pour récupérer.
Problèmes fréquents
tryCatch() avale l’erreur en silence, vous ne pouvez donc pas déboguer. Un gestionnaire qui se contente de renvoyer NULL (ou un message) cache ce qui a mal tourné — pendant le développement, vous fixez une sortie vide sans le moindre indice. Pendant la construction, journalisez la vraie erreur avant de renvoyer la valeur de repli : error = function(e) { message(e$message); NULL } l’envoie à la console pour que vous puissiez la voir, tandis que les utilisateurs obtiennent toujours la valeur sûre par défaut. N’expédiez pas un tryCatch() qui jette entièrement e.
Envelopper une portée trop large. Un tryCatch() autour du render entier intercepte toutes les erreurs — y compris les bogues que vous préféreriez voir — et les transforme toutes en la même valeur de repli, de sorte qu’une vraie erreur de code ressemble à un problème de données. N’enveloppez que la ligne qui peut véritablement échouer (le read.csv(), l’ajustement), pas le corps entier. Une portée étroite intercepte l’échec attendu et laisse les vrais bogues remonter.
Afficher une erreur brute aux utilisateurs. Renvoyer e$message directement à la sortie (error = function(e) e$message) ne fait qu’échanger une erreur rouge contre une autre, technique et légèrement différente — cannot open the connection ne signifie toujours rien pour un utilisateur non technique. Renvoyez plutôt un message simple et actionnable (« Could not read the file. Please upload a valid CSV. »), et gardez le détail technique dans le journal.
Questions fréquentes
Remplacez l’erreur rouge brute par quelque chose d’utile, choisi selon le pourquoi le code ne peut pas s’exécuter. Enveloppez le code risqué dans tryCatch({ ... }, error = function(e) <safe default>) pour qu’un vrai échec renvoie une valeur de repli au lieu de planter ; utilisez validate(need(cond, "message")) pour afficher un message convivial quand l’application n’est pas prête ; et utilisez req(input$x) pour attendre silencieusement que les entrées existent. Renvoyez des valeurs sûres par défaut — NULL, un tableau vide, un graphique de remplacement — pour qu’une sortie n’affiche jamais une trace d’appels.
Ils répondent à trois questions différentes. req(input$x) vérifie qu’une entrée est prête et s’arrête silencieusement sinon — pour le cas vide-au-démarrage. validate(need(cond, "message")) s’arrête avec un message convivial dans la sortie — pour un état pas-encore-prêt que vous voulez expliquer. tryCatch({ ... }, error = ...) intercepte un vrai échec d’exécution (un mauvais fichier, un ajustement raté) et exécute une valeur de repli pour que l’application récupère. Règle générale : req() pour attendre, validate() pour expliquer, tryCatch() pour récupérer.
Pour un état pas-encore-prêt, utilisez validate(need(input$file, "Upload a CSV file to begin.")) en tête du render — il affiche votre message à la place de la sortie. Pour un vrai échec, enveloppez le code risqué dans tryCatch() et renvoyez un message simple depuis le gestionnaire error = : tryCatch(read.csv(path), error = function(e) "Could not read the file."). Tous deux remplacent l’erreur rouge brute par quelque chose sur lequel un utilisateur peut agir.
Renvoyez une valeur que la sortie peut afficher sans produire d’erreur — depuis un gestionnaire tryCatch() ou une vérification if. Valeurs sûres par défaut courantes : NULL ou un data frame vide depuis un reactive (puis protégez les lecteurs avec req()), ou un graphique de remplacement (plot.new(); text(0.5, 0.5, "No data yet.")) au lieu de tracer des données vides. L’application garde sa forme ; l’utilisateur voit « pas de données pour l’instant » plutôt qu’une trace d’appels rouge.
Shiny affiche les erreurs (stop()) sous forme de texte rouge dans la sortie, mais les avertissements (warning()) ne vont qu’à la console R ou au journal du serveur — le code continue de tourner, donc l’utilisateur ne les voit jamais. Si un avertissement signifie que le résultat n’est pas fiable, gérez-le explicitement avec un gestionnaire warning = à l’intérieur de tryCatch() et repliez-vous sur une valeur sûre par défaut, de la même façon que vous géreriez une erreur.
Testez vos connaissances
Partez d’une application avec un fileInput("file", "Upload CSV") et un tableOutput("table"). Écrivez le server pour que output$table lise le CSV téléversé et l’affiche — mais si aucun fichier n’est encore téléversé, il attend silencieusement, et si le fichier ne peut pas être lu, il affiche le message « Could not read the file. » au lieu d’une erreur rouge. Utilisez req() et tryCatch() ensemble.
Placez req(input$file) en premier pour que la sortie attende qu’un fichier existe. Puis enveloppez read.csv(input$file$datapath) dans tryCatch({ ... }, error = function(e) ...), en renvoyant un data frame d’une ligne ou un message depuis le gestionnaire pour que le tableau ait quelque chose de sûr à afficher.
server <- function(input, output, session) {
output$table <- renderTable({
req(input$file) # wait silently for an upload
tryCatch({
read.csv(input$file$datapath) # the risky step
}, error = function(e) {
data.frame(Message = "Could not read the file.")
})
})
}req(input$file) garde la sortie vide jusqu’à ce que l’utilisateur téléverse quelque chose — pas d’erreur rouge au démarrage. Le tryCatch() n’enveloppe que le read.csv(), de sorte qu’un véritable échec de lecture renvoie un data frame net d’une ligne au lieu d’une trace d’appels, tandis qu’une lecture réussie affiche les données comme d’habitude.
Votre application ajuste un modèle à l’intérieur de renderPlot(), et sur certaines entrées utilisateur l’ajustement lève une erreur — faisant clignoter du texte rouge dans la zone du graphique. Vous voulez que le graphique se replie sur un remplacement vide accompagné d’une note à la place. Quel outil convient ?
A. req(input$x) en tête du render. B. validate(need(input$x, "Pick a value.")) à l’intérieur du render. C. tryCatch({ fit ... }, error = function(e) { plot.new(); text(...) }). D. Déplacer l’ajustement dans un observeEvent().
C. L’ajustement peut véritablement échouer à l’exécution, il vous faut donc intercepter l’erreur et récupérer — tryCatch(..., error = ...) exécute votre graphique de remplacement quand l’ajustement lève une erreur. A (req()) attend seulement qu’une entrée existe ; il ne peut pas intercepter un échec dans du code qui, lui, s’exécute. B affiche un message pour un état pas-encore-prêt, pas une erreur d’exécution. D déplace où le code s’exécute mais n’intercepte pas l’erreur.
Conclusion
Une application Shiny prête pour la production échoue en douceur au lieu de clignoter en rouge. Choisissez l’outil selon le pourquoi le code ne peut pas s’exécuter : req() pour attendre silencieusement les entrées, validate(need()) pour afficher un message convivial quand l’application n’est pas prête, et tryCatch() pour intercepter un vrai échec et renvoyer une valeur sûre par défaut — NULL, un tableau vide, un graphique de remplacement. N’enveloppez que la ligne qui peut véritablement échouer, journalisez la vraie erreur pendant que vous développez, et ne tendez jamais à l’utilisateur une trace d’appels brute. Avec cela en place, une seule mauvaise entrée ne fait plus tomber une sortie — l’application reste debout et dit à l’utilisateur quoi faire.
Cela complète la série sur le server — vous pouvez désormais conserver un état, répondre aux événements, traiter des données, ramifier selon l’entrée, et récupérer d’un échec. L’interactivité est la suite : rendre l’application vivante avec une interface dynamique, des mises à jour, et un retour plus riche.
Leçons connexes
- Construisez à partir d’ici : la logique conditionnelle — la leçon précédente, où
req()etvalidate(need())protègent le server (les mécanismes que cette leçon utilise comme outils de gestion des erreurs) ; et la programmation réactive pour le modèlereactive()sous-jacent (le prérequis). Ensuite l’interactivité rend l’application vivante. - Pour aller plus loin : Logique du server Shiny — la série complète. · Shiny UI — la couche interface. · Science computationnelle & IA — le pilier.
Réutilisation
Citation
@online{2026,
author = {},
title = {Gestion des erreurs Shiny : tryCatch, validate(need), req et
valeurs de repli sûres},
date = {2026-06-28},
url = {https://www.datanovia.com/learn/programming/shiny/server/error-handling},
langid = {fr}
}