Precaución
Esta propuesta cambiará a try-expressions
como un apporach más idiomático para este problema. Lea más en #4 y #5.
Se necesita ayuda en su reescritura :)
Advertencia
Esta propuesta está activamente en desarrollo, y las contribuciones son bienvenidas.
Esta propuesta introduce un nuevo operador,? ?=
(Asignación segura) , que simplifica el manejo de errores al transformar el resultado de una función en una tupla. Si la función arroja un error, el operador devuelve [error, null]
; Si la función se ejecuta correctamente, devuelve [null, result]
. Este operador es compatible con las promesas, las funciones de Async y cualquier valor que implementa el método Symbol.result
.
Por ejemplo, al realizar operaciones de E/S o interactuar con API basadas en la promesa, los errores pueden ocurrir inesperadamente en tiempo de ejecución. Descuidar manejar estos errores puede conducir a un comportamiento no deseado y posibles vulnerabilidades de seguridad.
const [ error , response ] ? = await fetch ( "https://arthur.place" )
Symbol.result
?=
)using
la declaracióndata
primero??=
Con funciones y objetos sin Symbol.result
¿Con qué frecuencia ha visto código así?
async function getData ( ) {
const response = await fetch ( "https://api.example.com/data" )
const json = await response . json ( )
return validationSchema . parse ( json )
}
El problema con la función anterior es que puede fallar en silencio, potencialmente bloqueando su programa sin ninguna advertencia explícita.
fetch
puede rechazar.json
puede rechazar.parse
puede lanzar. Para abordar esto, proponemos la adopción de un nuevo operador ?=
, Que facilita el manejo de errores más conciso y legible.
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
}
Consulte lo que esta propuesta no pretende resolver la sección para comprender las limitaciones de esta propuesta.
Esta propuesta tiene como objetivo introducir las siguientes características:
Symbol.result
Cualquier objeto que implementa el método Symbol.result
se puede usar con el operador ?=
.
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')
El método Symbol.result
debe devolver una tupla, donde el primer elemento representa el error y el segundo elemento representa el resultado.
¿Por qué no data
primero?
?=
) El operador ?=
Invoca el Symbol.result
El método de resultación en el objeto o función en el lado derecho del operador, asegurando que los errores y los resultados se manejen constantemente de manera estructurada.
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)
El resultado debe ajustarse al formato [error, null | undefined]
o [null, data]
.
Cuando el operador ?=
Se usa dentro de una función, todos los parámetros pasados a esa función se reenvían al método Symbol.result
.
declare function action ( argument : string ) : string
const [ error , data ] ? = action ( argument1 , argument2 , ... )
// const [error, data] = action[Symbol.result](argument, argument2, ...)
Cuando el operador ?=
Se usa con un objeto, no se pasan parámetros al método Symbol.result
.
declare const obj : { [ Symbol . result ] : ( ) => any }
const [ error , data ] ? = obj
// const [error, data] = obj[Symbol.result]()
La tupla [error, null]
se genera en el primer error encontrado. Sin embargo, si los data
en una tupla [null, data]
también implementan un método de Symbol.result
, se invocarán de manera recursiva.
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')
Estos comportamientos facilitan el manejo de diversas situaciones que involucran promesas u objetos con Symbol.result
.
async function(): Promise<T>
function(): T
function(): T | Promise<T>
Estos casos pueden involucrar de 0 a 2 niveles de objetos anidados con Symbol.result
.
Una Promise
es la única otra implementación, además de Function
, que se puede usar con el operador ?=
.
const promise = getPromise ( )
const [ error , data ] ? = await promise
// const [error, data] = await promise[Symbol.result]()
Es posible que hayas notado que await
y ?=
Se pueden usar juntos, y eso está perfectamente bien. Debido a la función de manejo recursivo, no hay problemas para combinarlos de esta manera.
const [ error , data ] ? = await getPromise ( )
// const [error, data] = await getPromise[Symbol.result]()
La ejecución seguirá esta orden:
getPromise[Symbol.result]()
podría lanzar un error cuando se le llama (si es una función sincrónica que devuelve una promesa).error
y la ejecución se detendrá.data
. Dado que data
son una promesa y las promesas tienen un Symbol.result
.error
y la ejecución se detendrá.data
.using
la declaración La declaración using
o await using
también debería funcionar con el operador ?=
. Funcionará de manera similar a un estándar using x = y
.
Tenga en cuenta que los errores lanzados al eliminar un recurso no son atrapados por el operador ?=
, Así como no son manejados por otras características actuales.
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
El flujo de gestión using
se aplica solo cuando error
es null
o undefined
, y a
es verdad y tiene un Symbol.dispose
.
El bloque try {}
rara vez es útil, ya que su alcance carece de importancia conceptual. A menudo funciona más como una anotación de código en lugar de una construcción de flujo de control. A diferencia de los bloques de flujo de control, no hay un estado de programa que sea significativo solo dentro de un bloque de try {}
.
En contraste, el bloque catch {}
es el flujo de control real, y su alcance es significativo y relevante.
El uso de bloques try/catch
tiene dos problemas de sintaxis principales :
// 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
primero? En GO, la convención es colocar la variable de datos primero, y puede preguntarse por qué no seguimos el mismo enfoque en JavaScript. En Go, esta es la forma estándar de llamar a una función. Sin embargo, en JavaScript, ya tenemos la opción de usar const data = fn()
y elegir ignorar el error, que es precisamente el problema que estamos tratando de abordar.
Si alguien está usando ?=
Como su operador de tarea, es porque quiere asegurarse de manejar los errores y evitar olvidarlos. Colocar los datos primero contradeciría este principio, ya que prioriza el resultado sobre el manejo de errores.
// 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 desea suprimir el error (que es diferente de ignorar la posibilidad de que una función arroje un error), simplemente puede hacer lo siguiente:
// This suppresses the error (ignores it and doesn't re-throw it)
const [ , data ] ? = fn ( )
Este enfoque es mucho más explícito y legible porque reconoce que puede haber un error, pero indica que no le importa.
El método anterior también se conoce como "prueba calaboca de prueba" (un término brasileño) y puede reescribirse como:
let data
try {
data = fn ( )
} catch { }
Discusión completa sobre este tema en el #13 si el lector está interesado.
Esta propuesta se puede poliizar utilizando el código proporcionado en polyfill.js
.
Sin embargo, el operador ?=
No se puede polijar directamente. Cuando se dirige a entornos de JavaScript más antiguos, se debe usar un postprocesador para transformar el operador ?=
En las llamadas correspondientes [Symbol.result]
.
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 ] ( )
?=
Con funciones y objetos sin Symbol.result
Si la función u objeto no implementa un método Symbol.result
, el operador ?=
Debe lanzar un TypeError
.
El operador ?=
La propuesta de Symbol.result
no introduce una nueva lógica en el idioma. De hecho, todo lo que esta propuesta tiene como objetivo lograr ya se puede lograr con características de lenguaje actuales, aunque detalladas y propensas a errores .
try {
// try expression
} catch ( error ) {
// catch code
}
// or
promise // try expression
. catch ( ( error ) => {
// catch code
} )
es equivalente a:
const [ error , data ] ? = expression
if ( error ) {
// catch code
} else {
// try code
}
Este patrón está arquitectónicamente presente en muchos idiomas:
?
OperadorResult
try?
Operadortry
la palabra clave Si bien esta propuesta no puede ofrecer el mismo nivel de seguridad de tipo o rigor que estos idiomas, debido a la naturaleza dinámica de JavaScript y el hecho de que la declaración throw
puede lanzar cualquier cosa, tiene como objetivo hacer que el manejo de errores sea más consistente y manejable.
Strict Type Enforcement para errores : la declaración throw
en JavaScript puede lanzar cualquier tipo de valor. Esta propuesta no impone la seguridad del tipo al manejo de errores y no introducirá tipos en el idioma. Tampoco se extenderá a TypeScript. Para obtener más información, consulte Microsoft/TypeScript#13219.
Manejo automático de errores : si bien esta propuesta facilita el manejo de errores, no maneja automáticamente los errores para usted. Todavía deberá escribir el código necesario para administrar errores; La propuesta simplemente tiene como objetivo hacer que este proceso sea más fácil y más consistente.
Si bien esta propuesta todavía está en sus primeras etapas, conocemos varias limitaciones y áreas que necesitan un mayor desarrollo:
Nomenclatura para Symbol.result
Métodos de resultas : Necesitamos establecer un término para objetos y funciones que implementen los métodos Symbol.result
. Los términos posibles incluyen resultables o erróneo , pero esto debe definirse.
Uso de this
: el comportamiento de this
dentro del contexto del Symbol.result
aún no se ha probado ni documentado. Esta es un área que requiere más exploración y documentación.
Manejo finally
bloques : actualmente no hay mejoras de sintaxis para el manejo finally
bloques. Sin embargo, aún puede usar el bloque finally
como lo haría normalmente:
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
}
Esta propuesta se encuentra en sus primeras etapas, y agradecemos su opinión para ayudar a refinarla. No dude en abrir un problema o enviar una solicitud de extracción con sus sugerencias.
¡Cualquier contribución es bienvenida!
tuple-it
, que introduce un concepto similar pero modifica los prototipos Promise
y Function
, un enfoque que es menos ideal.Esta propuesta tiene licencia bajo la licencia MIT.