Prudence
Cette proposition changera en try-expressions
car c'est un apporach plus idiomatique à ce problème. En savoir plus sur les n ° 4 et # 5.
L'aide sur sa réécriture est nécessaire :)
Avertissement
Cette proposition est activement en cours de développement et les contributions sont les bienvenues.
Cette proposition introduit un nouvel opérateur , ?=
(Affectation sûre) , qui simplifie la gestion des erreurs en transformant le résultat d'une fonction en tuple. Si la fonction lance une erreur, l'opérateur renvoie [error, null]
; Si la fonction s'exécute avec succès, il renvoie [null, result]
. Cet opérateur est compatible avec les promesses, les fonctions asynchrones et toute valeur qui implémente la méthode Symbol.result
.
Par exemple, lors de l'exécution des opérations d'E / S ou de l'interaction avec des API basées sur les promesses, des erreurs peuvent se produire de façon inattendue au moment de l'exécution. La négligence de gérer ces erreurs peut entraîner un comportement involontaire et des vulnérabilités de sécurité potentielles.
const [ error , response ] ? = await fetch ( "https://arthur.place" )
Symbol.result
?=
)using
de la déclarationdata
en premier??=
Avec des fonctions et des objets sans Symbol.result
Combien de fois avez-vous vu du code comme celui-ci?
async function getData ( ) {
const response = await fetch ( "https://api.example.com/data" )
const json = await response . json ( )
return validationSchema . parse ( json )
}
Le problème avec la fonction ci-dessus est qu'il peut échouer en silence, potentiellement écraser votre programme sans aucun avertissement explicite.
fetch
peut rejeter.json
peut rejeter.parse
peut lancer. Pour y remédier, nous proposons l'adoption d'un nouvel opérateur , ?=
, qui facilite la gestion des erreurs plus concise et lisible.
async function getData ( ) {
const [ requestError , response ] ? = await fetch (
"https://api.example.com/data"
)
if ( requestError ) {
handleRequestError ( requestError )
return
}
const [ parseError , json ] ? = await response . json ( )
if ( parseError ) {
handleParseError ( parseError )
return
}
const [ validationError , data ] ? = validationSchema . parse ( json )
if ( validationError ) {
handleValidationError ( validationError )
return
}
return data
}
Veuillez vous référer à ce que cette proposition vise à résoudre la section pour comprendre les limites de cette proposition.
Cette proposition vise à introduire les fonctionnalités suivantes:
Symbol.result
Tout objet qui implémente la méthode Symbol.result
peut être utilisé avec l'opérateur ?=
.
function example ( ) {
return {
[ Symbol . result ] ( ) {
return [ new Error ( "123" ) , null ]
} ,
}
}
const [ error , result ] ? = example ( ) // Function.prototype also implements Symbol.result
// const [error, result] = example[Symbol.result]()
// error is Error('123')
La méthode Symbol.result
doit renvoyer un tuple, où le premier élément représente l'erreur et le deuxième élément représente le résultat.
Pourquoi pas data
en premier?
?=
) L'opérateur ?=
Invoque la méthode de Symbol.result
.
const obj = {
[ Symbol . result ] ( ) {
return [ new Error ( "Error" ) , null ]
} ,
}
const [ error , data ] ? = obj
// const [error, data] = obj[Symbol.result]()
function action ( ) {
return 'data'
}
const [ error , data ] ? = action ( argument )
// const [error, data] = action[Symbol.result](argument)
Le résultat doit être conforme au format [error, null | undefined]
ou [null, data]
.
Lorsque l'opérateur ?=
Est utilisé dans une fonction, tous les paramètres transmis à cette fonction sont transmis à la méthode Symbol.result
.
declare function action ( argument : string ) : string
const [ error , data ] ? = action ( argument1 , argument2 , ... )
// const [error, data] = action[Symbol.result](argument, argument2, ...)
Lorsque l'opérateur ?=
Est utilisé avec un objet, aucun paramètre n'est transmis à la méthode Symbol.result
.
declare const obj : { [ Symbol . result ] : ( ) => any }
const [ error , data ] ? = obj
// const [error, data] = obj[Symbol.result]()
Le tuple [error, null]
est généré lors de la première erreur rencontrée. Cependant, si les data
d'une [null, data]
Tuple implémentent également une méthode Symbol.result
.
const obj = {
[ Symbol . result ] ( ) {
return [
null ,
{
[ Symbol . result ] ( ) {
return [ new Error ( "Error" ) , null ]
} ,
} ,
]
} ,
}
const [ error , data ] ? = obj
// const [error, data] = obj[Symbol.result]()
// error is Error('string')
Ces comportements facilitent la manipulation de diverses situations impliquant des promesses ou des objets avec des méthodes Symbol.result
:
async function(): Promise<T>
function(): T
function(): T | Promise<T>
Ces cas peuvent impliquer des niveaux de 0 à 2 objets imbriqués avec des méthodes Symbol.result
, et l'opérateur est conçu pour les gérer correctement.
Une Promise
est la seule autre implémentation, en plus de Function
, qui peut être utilisée avec l'opérateur ?=
.
const promise = getPromise ( )
const [ error , data ] ? = await promise
// const [error, data] = await promise[Symbol.result]()
Vous avez peut-être remarqué que await
et ?=
Peut être utilisé ensemble, et c'est parfaitement bien. En raison de la fonction de manipulation récursive, il n'y a aucun problème à les combiner de cette manière.
const [ error , data ] ? = await getPromise ( )
// const [error, data] = await getPromise[Symbol.result]()
L'exécution suivra cette commande:
getPromise[Symbol.result]()
peut lancer une erreur lorsqu'elle est appelée (s'il s'agit d'une fonction synchrone renvoyant une promesse).error
et l'exécution s'arrêtera.data
. Étant donné que data
sont une promesse et que les promesses ont une méthode Symbol.result
.error
et l'exécution s'arrêtera.data
.using
de la déclaration L'instruction using
OU await using
devrait également fonctionner avec l'opérateur ?=
. Il fonctionnera de manière similaire à une norme using x = y
.
Notez que les erreurs lancées lors de l'élimination d'une ressource ne sont pas capturées par l'opérateur ?=
, Tout comme ils ne sont pas gérés par d'autres fonctionnalités actuelles.
try {
using a = b
} catch ( error ) {
// handle
}
// now becomes
using [ error , a ] ? = b
// or with async
try {
await using a = b
} catch ( error ) {
// handle
}
// now becomes
await using [ error , a ] ? = b
Le flux de gestion using
n'est appliqué que lorsque error
est null
ou undefined
, et a
est la vérité et a une méthode Symbol.dispose
.
Le bloc try {}
est rarement utile, car sa portée manque de signification conceptuelle. Il fonctionne souvent plus comme une annotation de code plutôt que comme une construction de flux de contrôle. Contrairement aux blocs de flux de contrôle, il n'y a pas d'état de programme qui n'est significatif que dans un bloc try {}
.
En revanche, le bloc catch {}
est un flux de contrôle réel, et sa portée est significative et pertinente.
L'utilisation de blocs try/catch
a deux problèmes de syntaxe principale :
// Nests 1 level for each error handling block
async function readData ( filename ) {
try {
const fileContent = await fs . readFile ( filename , "utf8" )
try {
const json = JSON . parse ( fileContent )
return json . data
} catch ( error ) {
handleJsonError ( error )
return
}
} catch ( error ) {
handleFileError ( error )
return
}
}
// Declares reassignable variables outside the block, which is undesirable
async function readData ( filename ) {
let fileContent
let json
try {
fileContent = await fs . readFile ( filename , "utf8" )
} catch ( error ) {
handleFileError ( error )
return
}
try {
json = JSON . parse ( fileContent )
} catch ( error ) {
handleJsonError ( error )
return
}
return json . data
}
data
en premier? En Go, la convention est de placer la variable de données en premier, et vous pourriez vous demander pourquoi nous ne suivons pas la même approche en JavaScript. Dans GO, c'est le moyen standard d'appeler une fonction. Cependant, dans JavaScript, nous avons déjà la possibilité d'utiliser const data = fn()
et de choisir d'ignorer l'erreur, ce qui est précisément le problème que nous essayons de résoudre.
Si quelqu'un utilise ?=
Comme opérateur d'affectation, c'est parce qu'il veut s'assurer qu'il gère les erreurs et évite de les oublier. Placer les données d'abord contredire ce principe, car il privilégie le résultat sur la gestion des erreurs.
// ignores errors!
const data = fn ( )
// Look how simple it is to forget to handle the error
const [ data ] ? = fn ( )
// This is the way to go
const [ error , data ] ? = fn ( )
Si vous souhaitez supprimer l'erreur (ce qui est différent d'ignorer la possibilité d'une fonction qui a lancé une erreur), vous pouvez simplement faire ce qui suit:
// This suppresses the error (ignores it and doesn't re-throw it)
const [ , data ] ? = fn ( )
Cette approche est beaucoup plus explicite et lisible car elle reconnaît qu'il pourrait y avoir une erreur, mais indique que vous ne vous en souciez pas.
La méthode ci-dessus est également connue sous le nom de «calaboca try-plach» (un terme brésilien) et peut être réécrite comme:
let data
try {
data = fn ( )
} catch { }
Discussion complète sur ce sujet au n ° 13 si le lecteur est intéressé.
Cette proposition peut être polyvalente en utilisant le code fourni sur polyfill.js
.
Cependant, l'opérateur ?=
Ne peut pas être polyvalent directement. Lors du ciblage des environnements JavaScript plus anciens, un post-processeur doit être utilisé pour transformer l'opérateur ?=
En appels [Symbol.result]
correspondants.
const [ error , data ] ? = await asyncAction ( arg1 , arg2 )
// should become
const [ error , data ] = await asyncAction [ Symbol . result ] ( arg1 , arg2 )
const [ error , data ] ? = action ( )
// should become
const [ error , data ] = action [ Symbol . result ] ( )
const [ error , data ] ? = obj
// should become
const [ error , data ] = obj [ Symbol . result ] ( )
?=
Avec des fonctions et des objets sans Symbol.result
Si la fonction ou l'objet n'implémente ?=
de méthode TypeError
Symbol.result
.
L'opérateur ?=
La proposition Symbol.result
. En fait, tout ce que cette proposition vise à réaliser peut déjà être accompli avec les caractéristiques de la langue actuelles, bien que verbeuses et sujettes aux erreurs .
try {
// try expression
} catch ( error ) {
// catch code
}
// or
promise // try expression
. catch ( ( error ) => {
// catch code
} )
équivaut à:
const [ error , data ] ? = expression
if ( error ) {
// catch code
} else {
// try code
}
Ce modèle est architectural présent dans de nombreuses langues:
?
OpérateurResult
try?
Opérateurtry
le mot-clé Bien que cette proposition ne puisse pas offrir le même niveau de sécurité ou de rigueur de type que ces langues - en raison de la nature dynamique de JavaScript et du fait que l'instruction throw
peut tout lancer - il vise à rendre la gestion des erreurs plus cohérente et gérable.
Application de type stricte pour les erreurs : l'instruction throw
en JavaScript peut lancer n'importe quel type de valeur. Cette proposition n'impose pas la sécurité de type à la gestion des erreurs et n'introduira pas les types dans la langue. Il ne sera pas non plus étendu à TypeScript. Pour plus d'informations, voir Microsoft / TypeScript # 13219.
Gestion automatique des erreurs : Bien que cette proposition facilite la gestion des erreurs, elle ne gère pas automatiquement les erreurs pour vous. Vous devrez toujours rédiger le code nécessaire pour gérer les erreurs; La proposition vise simplement à rendre ce processus plus facile et plus cohérent.
Bien que cette proposition en soit encore à ses débuts, nous sommes conscients de plusieurs limitations et zones qui nécessitent un développement ultérieur:
Nomenclature pour Symbol.result
Méthodes : Nous devons établir un terme pour les objets et les fonctions qui implémentent les méthodes Symbol.result
. Result. Les termes possibles incluent la résulsion ou l'erreur , mais cela doit être défini.
L'utilisation de this
: le comportement de this
dans le contexte du Symbol.result
n'a pas encore été testé ou documenté. Il s'agit d'un domaine qui nécessite une exploration et une documentation plus approfondies.
Gestion des blocs finally
: il n'y a actuellement aucune amélioration de syntaxe pour la manipulation des blocs finally
. Cependant, vous pouvez toujours utiliser le bloc finally
comme vous le feriez normalement:
try {
// try code
} catch {
// catch errors
} finally {
// finally code
}
// Needs to be done as follows
const [ error , data ] ? = action ( )
try {
if ( error ) {
// catch errors
} else {
// try code
}
} finally {
// finally code
}
Cette proposition en est à ses débuts et nous nous félicitons de votre contribution pour vous aider à affiner. N'hésitez pas à ouvrir un problème ou à soumettre une demande de traction avec vos suggestions.
Toute contribution est la bienvenue!
tuple-it
, qui introduit un concept similaire mais modifie les prototypes Promise
et Function
- une approche moins idéale.Cette proposition est autorisée en vertu de la licence du MIT.