注意
この提案は、この問題に対するより慣用的なアプローチであるため、 try-expressions
に変更されます。 #4と#5の詳細を読んでください。
その書き換えの助けが必要です:)
警告
この提案は積極的に開発中であり、貢献を歓迎します。
この提案では、新しいオペレーター、 ?=
(安全な割り当て)が紹介されます。これにより、関数の結果をタプルに変換することによりエラー処理が簡素化されます。関数がエラーをスローする場合、演算子は[error, null]
を返します。関数が正常に実行されると、 [null, result]
を返します。この演算子は、約束、非同期関数、およびSymbol.result
メソッドを実装する任意の値と互換性があります。
たとえば、I/O操作を実行したり、約束ベースのAPIと対話する場合、実行時にエラーが予期せず発生する可能性があります。これらのエラーを処理することを無視すると、意図しない行動や潜在的なセキュリティの脆弱性につながる可能性があります。
const [ error , response ] ? = await fetch ( "https://arthur.place" )
Symbol.result
?=
)using
data
をしないのはなぜですか?Symbol.result
のない関数とオブジェクトを使用して?=
を使用しますこのようなコードをどのくらいの頻度で見ましたか?
async function getData ( ) {
const response = await fetch ( "https://api.example.com/data" )
const json = await response . json ( )
return validationSchema . parse ( json )
}
上記の関数の問題は、明示的な警告なしにプログラムをクラッシュさせる可能性があるため、静かに失敗する可能性があることです。
fetch
拒否できます。json
拒否できます。parse
投げることができます。これに対処するために、新しいオペレーターの採用を提案します。 ?=
は、より簡潔で読み取り可能なエラー処理を促進します。
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
}
この提案がセクションを解決することを目的としていないことを参照してください。この提案の限界を理解してください。
この提案は、次の機能を紹介することを目的としています。
Symbol.result
Symbol.result
メソッドを実装するオブジェクトは、 ?=
演算子で使用できます。
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')
Symbol.result
メソッドはタプルを返す必要があります。最初の要素はエラーを表し、2番目の要素は結果を表します。
最初にdata
をしないのはなぜですか?
?=
) ?=
演算子は、演算子の右側にあるオブジェクトまたは関数の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)
結果はフォーマット[error, null | undefined]
または[null, data]
。
?=
演算子が関数内で使用されると、その関数に渡されたすべてのパラメーターがSymbol.result
メソッドに転送されます。
declare function action ( argument : string ) : string
const [ error , data ] ? = action ( argument1 , argument2 , ... )
// const [error, data] = action[Symbol.result](argument, argument2, ...)
?=
演算子がオブジェクトで使用されると、パラメーターはSymbol.result
メソッドに渡されません。
declare const obj : { [ Symbol . result ] : ( ) => any }
const [ error , data ] ? = obj
// const [error, data] = obj[Symbol.result]()
[error, null]
タプルは、遭遇した最初のエラー時に生成されます。ただし、 [null, data]
タプルのdata
が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')
これらの動作により、約束やSymbol.result
を含むさまざまな状況の処理を促進します。
async function(): Promise<T>
function(): T
function(): T | Promise<T>
これらのケースには、 Symbol.result
メソッドを備えた0〜2レベルのネストされたオブジェクトが含まれる場合があり、演算子はすべてを正しく処理するように設計されています。
Promise
、 Function
に加えて、 ?=
演算子で使用できる他の唯一の実装です。
const promise = getPromise ( )
const [ error , data ] ? = await promise
// const [error, data] = await promise[Symbol.result]()
あなたはawait
ことに気づいたかもしれません、そして?=
一緒に使用できます、そしてそれはまったく問題ありません。再帰的な取り扱い機能により、この方法でそれらを組み合わせることに問題はありません。
const [ error , data ] ? = await getPromise ( )
// const [error, data] = await getPromise[Symbol.result]()
実行はこの注文に従います。
getPromise[Symbol.result]()
呼び出されたときにエラーをスローする可能性があります(約束を返す同期関数の場合)。error
に割り当てられ、実行が停止します。data
に割り当てられます。 data
約束であり、約束にはSymbol.result
持っているため、再帰的に処理されます。error
に割り当てられ、実行が停止します。data
に割り当てられます。using
ステートメントをusing
またはawait using
?=
演算子でも動作する必要があります。 using x = y
標準と同様に実行されます。
リソースを処分するときにスローされたエラーは、他の現在の機能によって処理されないように、 ?=
演算子によってキャッチされていないことに注意してください。
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
using
フローは、 error
がnull
またはundefined
の場合にのみ適用され、 a
真実であり、 Symbol.dispose
メソッドを持っています。
スコーピングには概念的な重要性がないため、 try {}
ブロックはめったに便利ではありません。多くの場合、コントロールフローコンストラクトではなく、コードアノテーションとして機能します。制御フローブロックとは異なり、 try {}
ブロック内でのみ意味のあるプログラム状態はありません。
対照的に、 catch {}
ブロックは実際の制御フローであり、そのスコーピングは意味があり、関連性があります。
try/catch
ブロックを使用するには、 2つの主要な構文の問題があります。
// 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
をしないのはなぜですか? GOでは、条約は最初にデータ変数を配置することであり、なぜJavaScriptで同じアプローチに従わないのか疑問に思うかもしれません。 Goでは、これが関数を呼び出す標準的な方法です。ただし、JavaScriptでは、 const data = fn()
を使用するオプションが既にあり、エラーを無視することを選択します。これは、正確には対処しようとしている問題です。
誰かが?=
割り当てオペレーターとして使用している場合、それは彼らがエラーを処理し、それらを忘れないようにしたいからです。データを最初に配置すると、エラー処理よりも結果を優先するため、この原則と矛盾します。
// 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 ( )
エラーを抑制したい場合(これは、関数がエラーを投げる可能性を無視することとは異なります)、次のことを実行できます。
// This suppresses the error (ignores it and doesn't re-throw it)
const [ , data ] ? = fn ( )
このアプローチは、エラーがある可能性があることを認めているが、気にしないことを示しているため、はるかに明確で読みやすいものです。
上記の方法は「トライキャッチカラボカ」(ブラジル語の用語)としても知られており、次のように書き直すことができます。
let data
try {
data = fn ( )
} catch { }
読者が興味を持っている場合、#13でこのトピックに関する完全な議論。
この提案は、 polyfill.js
で提供されたコードを使用してポリフィルできます。
ただし、 ?=
演算子自体は直接ポリフィージーできません。古いJavaScript環境をターゲットにする場合、ポストプロセッサを使用して、 ?=
演算子を対応する[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 ] ( )
Symbol.result
のない関数とオブジェクトを使用して?=
を使用します関数またはオブジェクトがSymbol.result
メソッドを実装していない場合、 ?=
演算子はTypeError
スローする必要があります。
?=
operatorとSymbol.result
の提案は、言語に新しいロジックを導入しません。実際、この提案が達成することを目的としているすべては、冗長でエラーが発生しやすい言語の特徴ではありますが、現在の現在ではすでに達成できます。
try {
// try expression
} catch ( error ) {
// catch code
}
// or
promise // try expression
. catch ( ( error ) => {
// catch code
} )
に相当します:
const [ error , data ] ? = expression
if ( error ) {
// catch code
} else {
// try code
}
このパターンは、多くの言語で建築的に存在します。
?
オペレーターResult
タイプtry?
オペレーターtry
この提案は、JavaScriptの動的な性質とthrow
ステートメントが何でも投げることができるという事実によると、これらの言語と同じレベルのタイプの安全性または厳格さを提供することはできませんが、エラー処理をより一貫性と管理しやすくすることを目指しています。
エラーの厳格なタイプの施行:JavaScriptのthrow
ステートメントは、あらゆるタイプの値を投げることができます。この提案は、エラー処理にタイプの安全性を課すものではなく、言語にタイプを導入することもありません。また、TypeScriptに拡張されません。詳細については、Microsoft/Typescript#13219を参照してください。
自動エラー処理:この提案はエラー処理を容易にしますが、エラーを自動的に処理しません。エラーを管理するために必要なコードを作成する必要があります。この提案は、このプロセスをより容易にし、より一貫性にすることを単に目指しています。
この提案はまだ初期段階にありますが、さらなる開発が必要ないくつかの制限と領域を認識しています。
Symbol.result
メソッドの命名法: Symbol.result
メソッドを実装するオブジェクトと関数の用語を確立する必要があります。考えられる用語には、結果が含まれますが、エラー可能ですが、これを定義する必要があります。
this
の使用: Symbol.result
のコンテキスト内でのthis
の動作はまだテストまたは文書化されていません。これは、さらなる調査とドキュメントを必要とする領域です。
finally
ブロック: finally
ブロックを処理するための構文の改善は現在ありません。ただし、通常のようにfinally
ブロックを使用できます。
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
}
この提案は初期段階にあり、それを改良するのに役立つ入力を歓迎します。お気軽に問題を開いたり、提案を受けてプルリクエストを送信したりしてください。
どんな貢献も大歓迎です!
Promise
とFunction
プロトタイプを変更するtuple-it
NPMパッケージは、それほど理想的ではないアプローチです。この提案は、MITライセンスに基づいてライセンスされています。