◆ try と catch でブロックスコープが分かれる
◆ catch で使うためだけに try-catch の外に宣言したくない
◆ エラー時に必要情報を Error オブジェクトに追加して throw する
◆ 最終的に try-catch を囲むブロック作って普通にやるのがよさそう

何度か書いてる try-catch のスコープ問題です
よくあるのがエラーが起きる可能性があるので try-catch で囲むと try の中で宣言した変数がその後で使えないというものです

catch で try の変数を使いたい

今回は try-catch の外じゃなくて catch 句の部分です

エラーが起きるかもしれない関数を用意しておきます
引数がマイナスならエラーを throw します

function mayThrowError(n) {
if (n < 0) throw new Error("negative value")
}

配列の処理中にエラーが起きると どこでエラーが起きたか知りたいことがあります
ブロックスコープがあるので catch 句で使いたい変数は外側で宣言しておかないといけません

const array = [1, 2, -3, 4]

let index = 0
try {
for (; index < array.length; index++) {
mayThrowError(array[index])
}
} catch (err) {
console.log("error at index: " + index)
}

var

var を使えば関数スコープにできるので そのまま使えます

try {
for (var index = 0; index < array.length; index++) {
mayThrowError(array[index])
}
} catch (err) {
console.log("error at index: " + index)
}

しかし できるだけ let/const で揃えたいですし try-catch の外のスコープでも存在するなら let で外に宣言してるほうがスコープ内の変数があることがわかりやすいです
存在する変数を探すときにブロック内を見なくていいのが ブロックスコープの良いところですからね

catch の処理を try で定義する

try の中で catch で実行する関数を作っておいて catch ではそれを実行するだけにします
try の中で作るので try 中のローカル変数が使えます

let catchCallback = null
try{
const x = 1
catchCallback = (err) => {
console.log(x)
}
throw new Error("throw")
}catch(err){
catchCallback(err)
}

関数に渡すわけでもないので callback とは言わない気もしますが気にしないでおきます
これにしても try で作った関数を catch に渡すために外側のスコープで宣言必要です
ただ 関数 1 つで済むので引き継ぎたい変数が多いときにはよいかもしれません

それでも catch 句がほぼ無意味ですし 見た目的にもわかりやすくないのでベストとは言えそうにないです

onerror

どうあるべきかを考えてみると エラーが起きた時の情報なので Error オブジェクトに含まれてるべきだと思います
通常はエラーのメッセージやスタックトレースなどだけですが その時の状態など補足情報も含まれていてほしいです

そういう方針で作ってたのがこれです

Function.prototype.onerror = function(callback) {
return (...args) => {
try {
return this(...args)
} catch (err) {
return callback(err)
}
}
}

try {
for (let index = 0; index < array.length; index++) {
mayThrowError.onerror(err => {
err.detail = { index }
throw err
})(array[index])
}
} catch (err) {
console.log("error at index: " + err.detail.index)
}

関数に onerror メソッドを作ってます
このメソッドに関数を渡すとエラー時の処理を設定できます
ここで Error オブジェクトにエラー情報を追加してそれを再 throw します

throw されたものは catch 句で受け取れて必要な変数の値が含まれているのでエラー時の処理は catch に書くことができます

try-catch なしで onerror だけでも使えますが catch に書いたほうがわかりやすそうなので onerror はあくまでエラー情報の収集だけにしています
それに onerror でエラー処理をしても終わった後に try の外まで出てくれないので エラーがあっても続行したいような時以外では onerror で処理するのは向いてないです

割と気にいってるのですが 結構特殊な方法になってる上に 関数呼び出し限定です
mayThrowError ではなくてそこで a[index] と書いて a が null だったりしたら onerror は設定できません
try-catch を二重にしてるようなものなので そうすればできますが見づらさが上がります
try-catch はただでさえ遅めなのに二重にするのは気が引けます

普通に

なんだかんだスタンダードな方法が良さそうです
catch で使うためだけの変数をスコープに残したくないなら try-catch 自体をブロックで囲みます

const array = [1, 2, -3, 4]

{
let index = 0
try {
for (; index < array.length; index++) {
mayThrowError(array[index])
}
} catch (err) {
console.log("error at index: " + index)
}
}

おまけ:with

なんか with ってこういうところで使えそうな気がしました

const array = [1, 2, -3, 4]

with({index: 0}){
try {
for (; index < array.length; index++) {
mayThrowError(array[index])
}
} catch (err) {
console.log("error at index: " + index)
}
}

if や for などでもない無理やりなブロックスコープより自然な感じがします

既視感

書いててすごく似たことというかこれ書かなかったっけと思いました
ただ 探しても記事はないんですよね
try-catch は書くたびに不満を感じていい感じにかけないかいろいろ試してるからそのせいかもしれません