Vorsicht
Dieser Vorschlag wird sich zu try-expressions
ändern, da es sich um ein idiomatischere Apporach für dieses Problem handelt. Lesen Sie mehr über #4 und #5.
Hilfe beim Umschreiben ist erforderlich :)
Warnung
Dieser Vorschlag befindet sich aktiv in der Entwicklung und Beiträge sind willkommen.
In diesem Vorschlag wird ein neuer Operator eingeführt ?=
Wenn die Funktion einen Fehler auslöst, gibt der Bediener [error, null]
zurück; Wenn die Funktion erfolgreich ausgeführt wird, gibt sie [null, result]
zurück. Dieser Bediener ist mit Versprechen, asynchronen Funktionen und jedem Wert kompatibel, der die Symbol.result
implementiert.
Beispielsweise können bei der Durchführung von E/A-Operationen oder der Interaktion mit vielversprechenden APIs Fehler zur Laufzeit unerwartet auftreten. Das Vernachlässigung dieser Fehler kann zu unbeabsichtigten Verhalten und potenziellen Sicherheitslücken führen.
const [ error , response ] ? = await fetch ( "https://arthur.place" )
Symbol.result
?=
)using
Anweisungdata
??=
Mit Funktionen und Objekten ohne Symbol.result
Wie oft haben Sie einen solchen Code gesehen?
async function getData ( ) {
const response = await fetch ( "https://api.example.com/data" )
const json = await response . json ( )
return validationSchema . parse ( json )
}
Das Problem mit der oben genannten Funktion ist, dass es stillschweigend scheitern kann und Ihr Programm möglicherweise ohne explizite Warnung abstürzt.
fetch
kann ablehnen.json
kann ablehnen.parse
kann werfen. Um dies zu beheben, schlagen wir die Einführung eines neuen Operators vor ?=
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
}
Bitte beziehen Sie sich auf den Abschnitt, was dieser Vorschlag nicht löst, um die Einschränkungen dieses Vorschlags zu verstehen.
Dieser Vorschlag zielt darauf ab, die folgenden Funktionen einzuführen:
Symbol.result
?=
Objekt, das das Symbol.result
implementiert.
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')
Das Symbol.result
-Methode muss ein Tupel zurückgeben, wobei das erste Element den Fehler darstellt und das zweite Element das Ergebnis darstellt.
Warum nicht zuerst data
?
?=
) Der ?=
Operator ruft das Symbol.result
auf.
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)
Das Ergebnis sollte dem Format entsprechen [error, null | undefined]
oder [null, data]
.
Wenn Symbol.result
?=
declare function action ( argument : string ) : string
const [ error , data ] ? = action ( argument1 , argument2 , ... )
// const [error, data] = action[Symbol.result](argument, argument2, ...)
Symbol.result
der ?=
declare const obj : { [ Symbol . result ] : ( ) => any }
const [ error , data ] ? = obj
// const [error, data] = obj[Symbol.result]()
Der [error, null]
-Tupel wird nach dem ersten aufgetretenen Fehler erzeugt. Wenn die data
in einem [null, data]
-Tupel jedoch auch ein Symbol.result
implementieren.
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')
Diese Verhaltensweisen erleichtern die Umgang mit verschiedenen Situationen, in denen Versprechen oder Objekte mit Symbol.result
beinhalten.
async function(): Promise<T>
function(): T
function(): T | Promise<T>
Diese Fälle können 0 bis 2 Ebenen verschachtelter Objekte mit Symbol.result
beinhalten.
Ein Promise
ist die einzige andere Implementierung, neben Function
, die mit dem Operator ?=
Verwendet werden kann.
const promise = getPromise ( )
const [ error , data ] ? = await promise
// const [error, data] = await promise[Symbol.result]()
Sie haben vielleicht bemerkt, dass await
und ?=
Kann zusammen verwendet werden, und das ist vollkommen in Ordnung. Aufgrund der rekursiven Handhabungsfunktion gibt es keine Probleme damit, sie auf diese Weise zu kombinieren.
const [ error , data ] ? = await getPromise ( )
// const [error, data] = await getPromise[Symbol.result]()
Die Ausführung folgt dieser Bestellung:
getPromise[Symbol.result]()
kann einen Fehler aufrufen, wenn es aufgerufen wird (wenn es sich um eine synchrone Funktion handelt, die ein Versprechen zurückgibt).error
zugewiesen und die Ausführung wird angehalten.data
zugeordnet. Da data
ein Versprechen sind und Versprechen ein Symbol.result
haben.error
zugewiesen, und die Ausführung wird gestoppt.data
zugewiesen.using
Anweisung Die using
oder await using
Anweisung sollte auch mit dem ?=
dem Bediener funktionieren. Es wird ähnlich wie eine Standardanweisung using x = y
einer Standard -Anweisung abgebildet.
Beachten Sie, dass Fehler, die bei der Entsorgung einer Ressource geworfen werden, nicht vom ?=
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
Der using
wird nur angewendet, wenn error
null
oder undefined
ist, und a
ist wahr und hat ein Symbol.dispose
.
Der try {}
-Block ist selten nützlich, da sein Scoping keine konzeptionelle Bedeutung hat. Es fungiert oft eher eher als Code -Annotation als als Kontrollflusskonstrukt. Im Gegensatz zu Kontrollflussblöcken gibt es keinen Programmstatus, der nur innerhalb eines try {}
Block aussagekräftig ist.
Im Gegensatz dazu ist der catch {}
-Block der tatsächliche Kontrollfluss und sein Scoping ist aussagekräftig und relevant.
Die Verwendung von try/catch
-Blöcken hat zwei Hauptsyntaxprobleme :
// 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
? In Go ist die Konvention, die Datenvariable zuerst zu platzieren, und Sie fragen sich vielleicht, warum wir nicht dem gleichen Ansatz in JavaScript verfolgen. In Go ist dies die Standardmethode, um eine Funktion aufzurufen. In JavaScript haben wir jedoch bereits die Möglichkeit const data = fn()
zu verwenden und den Fehler zu ignorieren, was genau das Problem ist, das wir angehen möchten.
Wenn jemand ?=
Das Erstieren der Daten würde diesem Prinzip zuerst widersprechen, da es das Ergebnis vor der Fehlerbehandlung priorisiert.
// 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 ( )
Wenn Sie den Fehler unterdrücken möchten (was sich von der Ignorierung der Möglichkeit einer Funktion unterscheidet , die einen Fehler wirft), können Sie einfach Folgendes tun:
// This suppresses the error (ignores it and doesn't re-throw it)
const [ , data ] ? = fn ( )
Dieser Ansatz ist viel expliziter und lesbarer, da er anerkennt, dass es möglicherweise einen Fehler gibt, aber darauf hinweist, dass Sie sich nicht darum kümmern.
Die obige Methode wird auch als "Try-Catch Calaboca" (ein brasilianischer Begriff) bezeichnet und kann als:
let data
try {
data = fn ( )
} catch { }
Vollständige Diskussion zu diesem Thema bei #13, wenn der Leser interessiert ist.
Dieser Vorschlag kann mit dem Code, der bei polyfill.js
bereitgestellt wurde, polyfillt werden.
Der ?=
Bei der Ausrichtung älterer JavaScript-Umgebungen sollte ein Postprozessor verwendet werden [Symbol.result]
um den ?=
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 ] ( )
?=
Mit Funktionen und Objekten ohne Symbol.result
?=
die Funktion oder TypeError
Objekt kein Symbol.result
implementiert.
Der ?=
Operator und das Symbol.result
In der Tat kann alles, was dieser Vorschlag erreichen soll, bereits mit aktuellen, ausführlichen und fehleranfälligen Sprachmerkmalen erreicht werden.
try {
// try expression
} catch ( error ) {
// catch code
}
// or
promise // try expression
. catch ( ( error ) => {
// catch code
} )
entspricht:
const [ error , data ] ? = expression
if ( error ) {
// catch code
} else {
// try code
}
Dieses Muster ist in vielen Sprachen architektonisch vorhanden:
?
OperatorResult
try?
Operatortry
das Keyword Obwohl dieser Vorschlag nicht das gleiche Maß an Sicherheit oder Strenge der Type wie diese Sprachen anbieten kann - dynamische Natur von JavaScript und die Tatsache, dass die throw
alles werfen kann -, zielt er darauf ab, die Fehlerbehandlung konsistenter und überschaubarer zu gestalten.
Strikte Typdurchsetzung für Fehler : Die throw
in JavaScript kann jede Art von Wert verleihen. Dieser Vorschlag erhöht keine Art Sicherheit beim Fehlerbehandlungen und führt keine Typen in die Sprache ein. Es wird auch nicht auf TypeScript erweitert. Weitere Informationen finden Sie unter Microsoft/TypeScript#13219.
Automatische Fehlerbehandlung : Während dieser Vorschlag die Fehlerbehandlung erleichtert, werden Fehler für Sie nicht automatisch behandelt. Sie müssen weiterhin den erforderlichen Code schreiben, um Fehler zu verwalten. Der Vorschlag zielt lediglich darauf ab, diesen Prozess einfacher und konsistenter zu machen.
Während dieser Vorschlag noch in den frühen Stadien liegt, sind wir uns mehreren Einschränkungen und Bereichen bewusst, die eine weitere Entwicklung benötigen:
Nomenklatur für Symbol.result
Symbol.result
Mögliche Begriffe sind ergebnisbar oder fehlerhaft , dies muss jedoch definiert werden.
Verwendung this
: Das this
im Kontext des Symbol.result
wurde noch nicht getestet oder dokumentiert. Dies ist ein Bereich, der weitere Explorationen und Dokumentationen erfordert.
Die Handhabung finally
blockiert : Derzeit gibt es keine Syntaxverbesserungen für den Umgang mit finally
Blöcken. Sie können jedoch den finally
Block immer noch wie gewohnt verwenden:
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
}
Dieser Vorschlag befindet sich in den frühen Phasen, und wir begrüßen Ihre Input, um sie zu verfeinern. Bitte eröffnen Sie ein Problem oder senden Sie eine Pull -Anfrage mit Ihren Vorschlägen.
Jeder Beitrag ist willkommen!
tuple-it
NPM-Paket, das ein ähnliches Konzept einführt, jedoch die Promise
und Function
verändert-ein weniger idealer Ansatz.Dieser Vorschlag ist im Rahmen der MIT -Lizenz lizenziert.