Cuidado
Esta proposta mudará para try-expressions
, pois é um mais idiomático para esse problema. Leia mais sobre #4 e 5.
Ajuda em sua reescrita é necessária :)
Aviso
Esta proposta está ativamente em desenvolvimento e as contribuições são bem -vindas.
Esta proposta apresenta um novo operador , ?=
(atribuição segura) , que simplifica o manuseio de erros transformando o resultado de uma função em uma tupla. Se a função lança um erro, o operador retorna [error, null]
; Se a função for executada com sucesso, ele retornará [null, result]
. Este operador é compatível com promessas, funções assíncronas e qualquer valor que implemente o método Symbol.result
.
Por exemplo, ao executar operações de E/S ou interagir com APIs baseadas em promessas, os erros podem ocorrer inesperadamente em tempo de execução. Negligenciar lidar com esses erros pode levar a um comportamento não intencional e potenciais vulnerabilidades de segurança.
const [ error , response ] ? = await fetch ( "https://arthur.place" )
Symbol.result
?=
)using
a instruçãodata
primeiro??=
Com funções e objetos sem Symbol.result
Com que frequência você viu código como este?
async function getData ( ) {
const response = await fetch ( "https://api.example.com/data" )
const json = await response . json ( )
return validationSchema . parse ( json )
}
O problema com a função acima é que ela pode falhar silenciosamente, potencialmente travando seu programa sem nenhum aviso explícito.
fetch
pode rejeitar.json
pode rejeitar.parse
pode jogar. Para resolver isso, propomos a adoção de um novo operador , ?=
, que facilita o tratamento de erros mais conciso e legível.
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 o que esta proposta não tem como objetivo resolver a seção para entender as limitações desta proposta.
Esta proposta tem como objetivo introduzir os seguintes recursos:
Symbol.result
Algum objeto que implementa o método Symbol.result
pode ser usado com o ?=
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')
O método Symbol.result
deve retornar uma tupla, onde o primeiro elemento representa o erro e o segundo elemento representa o resultado.
Por que não data
primeiro?
?=
) O operador ?=
Chama o método Symbol.result
no objeto ou função no lado direito do operador, garantindo que erros e resultados sejam manipulados de maneira estruturada de uma maneira estruturada.
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)
O resultado deve estar em conformidade com o formato [error, null | undefined]
ou [null, data]
.
Quando o operador ?=
É usado dentro de uma função, todos os parâmetros passados para essa função são encaminhados para o método Symbol.result
.
declare function action ( argument : string ) : string
const [ error , data ] ? = action ( argument1 , argument2 , ... )
// const [error, data] = action[Symbol.result](argument, argument2, ...)
Quando o operador ?=
É usado com um objeto, nenhum parâmetros é passado para o método Symbol.result
.
declare const obj : { [ Symbol . result ] : ( ) => any }
const [ error , data ] ? = obj
// const [error, data] = obj[Symbol.result]()
A tupla [error, null]
é gerada no primeiro erro encontrado. No entanto, se os data
em um tuple [null, data]
também implementarem um método de Symbol.result
, eles serão invocados recursivamente.
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')
Esses comportamentos facilitam o manuseio de várias situações envolvendo promessas ou objetos com Symbol.result
Methods:
async function(): Promise<T>
function(): T
function(): T | Promise<T>
Esses casos podem envolver 0 a 2 níveis de objetos aninhados com métodos Symbol.result
.
Uma Promise
é a única outra implementação, além Function
, que pode ser usada com o operador ?=
.
const promise = getPromise ( )
const [ error , data ] ? = await promise
// const [error, data] = await promise[Symbol.result]()
Você deve ter notado que await
e ?=
Pode ser usado em conjunto, e isso é perfeitamente bom. Devido ao recurso de manuseio recursivo, não há problemas em combiná -los dessa maneira.
const [ error , data ] ? = await getPromise ( )
// const [error, data] = await getPromise[Symbol.result]()
A execução seguirá esta ordem:
getPromise[Symbol.result]()
pode causar um erro quando chamado (se for uma função síncrona retornando uma promessa).error
e a execução será interrompida.data
. Como data
são uma promessa e as promessas têm um método de Symbol.result
.error
e a execução será interrompida.data
.using
a instrução O using
ou await using
a instrução também deve funcionar com o ?=
Operador. Ele terá um desempenho semelhante a um padrão using x = y
.
Observe que os erros lançados ao descartar um recurso não são capturados pelo operador ?=
, Assim como eles não são tratados por outros recursos atuais.
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
O fluxo de gerenciamento using
é aplicado apenas quando error
é null
ou undefined
, e a
é verdadeiro e tem um método Symbol.dispose
.
O bloco try {}
raramente é útil, pois seu escopo carece de significado conceitual. Geralmente funciona mais como uma anotação de código do que uma construção de fluxo de controle. Diferentemente dos blocos de fluxo de controle, não há estado de programa significativo apenas dentro de um bloco try {}
.
Por outro lado, o bloco catch {}
é o fluxo de controle real e seu escopo é significativo e relevante.
Usando os blocos try/catch
tem dois problemas principais de sintaxe :
// 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
primeiro? Em Go, a convenção é colocar a variável de dados primeiro e você pode se perguntar por que não seguimos a mesma abordagem no JavaScript. Em Go, esta é a maneira padrão de chamar uma função. No entanto, no JavaScript, já temos a opção de usar const data = fn()
e optar por ignorar o erro, que é precisamente o problema que estamos tentando abordar.
Se alguém está usando ?=
Como operador de atribuição, é porque deseja garantir que lide com erros e evite esquecê -los. A colocação dos dados primeiro contradizia esse princípio, pois priorizaria o resultado sobre o manuseio de erros.
// 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 ( )
Se você deseja suprimir o erro (que é diferente de ignorar a possibilidade de uma função lançar um erro), você pode simplesmente fazer o seguinte:
// This suppresses the error (ignores it and doesn't re-throw it)
const [ , data ] ? = fn ( )
Essa abordagem é muito mais explícita e legível porque reconhece que pode haver um erro, mas indica que você não se importa com isso.
O método acima também é conhecido como "Try-Patch Calaboca" (um mandato brasileiro) e pode ser reescrito como:
let data
try {
data = fn ( )
} catch { }
Conclua a discussão sobre este tópico no #13 se o leitor estiver interessado.
Esta proposta pode ser policiletada usando o código fornecido em polyfill.js
.
No entanto, o próprio operador ?=
Ao direcionar os ambientes JavaScript mais antigos, um pós-processador deve ser usado para transformar o operador ?=
Nas chamadas correspondentes [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 ] ( )
?=
Com funções e objetos sem Symbol.result
Se a função ou objeto não implementar um método de Symbol.result
, o ?=
Operador deverá lançar um TypeError
.
O ?=
Operador e a proposta Symbol.result
não introduzem nova lógica no idioma. De fato, tudo o que essa proposta visa alcançar já pode ser realizada com os recursos atuais, embora detalhados e propensos a erros .
try {
// try expression
} catch ( error ) {
// catch code
}
// or
promise // try expression
. catch ( ( error ) => {
// catch code
} )
é equivalente a:
const [ error , data ] ? = expression
if ( error ) {
// catch code
} else {
// try code
}
Esse padrão está arquitetonicamente presente em vários idiomas:
?
OperadorResult
try?
Operadortry
palavras -chave Embora essa proposta não possa oferecer o mesmo nível de segurança do tipo ou rigor que esses idiomas - devido à natureza dinâmica do JavaScript e ao fato de a declaração throw
poder lançar qualquer coisa - tem como objetivo tornar o manuseio de erros mais consistente e gerenciável.
Execução rígida do tipo para erros : a instrução throw
em JavaScript pode lançar qualquer tipo de valor. Esta proposta não impõe a segurança do tipo no manuseio de erros e não introduz tipos no idioma. Também não será estendido ao TypeScript. Para obter mais informações, consulte Microsoft/TypeScript#13219.
Manuseio automático de erros : Embora essa proposta facilite o manuseio de erros, ela não lida automaticamente erros para você. Você ainda precisará escrever o código necessário para gerenciar erros; A proposta simplesmente pretende tornar esse processo mais fácil e consistente.
Embora essa proposta ainda esteja em seus estágios iniciais, estamos cientes de várias limitações e áreas que precisam de maior desenvolvimento:
NOMENCLATURA PARA Symbol.result
Symbol.result
Os termos possíveis incluem resultante ou erro , mas isso precisa ser definido.
Uso this
: o comportamento this
dentro do contexto do Symbol.result
ainda não foi testado ou documentado. Esta é uma área que requer mais exploração e documentação.
finally
lidando com blocos : atualmente não há melhorias de sintaxe para lidar finally
blocos. No entanto, você ainda pode usar o bloco finally
como normalmente faria:
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 proposta está em seus estágios iniciais e recebemos sua opinião para ajudar a refiná -la. Sinta -se à vontade para abrir um problema ou enviar uma solicitação de tração com suas sugestões.
Qualquer contribuição é bem -vinda!
tuple-it
, que introduz um conceito semelhante, mas modifica os protótipos Promise
e Function
-uma abordagem que é menos ideal.Esta proposta é licenciada sob a licença do MIT.