安全赋值操作符
- Published on
今天关于在 JavaScript 中引入一个新的安全赋值操作符 ?=
的提案引起了不少关注。
const [error, value] ?= maybeThrows();
新的 ?=
操作符相当于在 try/catch
块中调用赋值右侧的内容,并返回一个数组。返回数组的第一个值是一个错误(如果在赋值中抛出了异常),第二个值是赋值的结果(如果没有抛出异常)。
常见的 try/catch 烦恼
我经常遇到一些代码,在赋值和 try/catch
块中显得相当丑陋。比如这样的代码:
let errorMsg;
try {
maybeThrow();
} catch (e) {
errorMsg = "An error message";
}
为了在 try/catch
块外使用 const
或 let
访问 errorMsg
,你必须在块外定义它。
非异步实现
最简单的情况是处理非异步函数。我能够快速编写一些测试用例和一个名为 tryCatch
的函数:
function tryCatch(fn, ...args) {
try {
return [undefined, fn.apply(null, args)];
} catch (e) {
return [e, undefined];
}
}
function throws() {
throw new Error("It threw");
}
// 返回一个和
// 打印 [ undefined, 2 ]
console.log(tryCatch(Math.sqrt, 4));
// 返回一个错误
// 打印 [ Error: 'It threw', undefined ]
console.log(tryCatch(throws));
tryCatch
使用给定的参数调用函数,并将其包装在 try/catch
块中。如果函数内部没有抛出异常,它会返回 [undefined, result]
;如果抛出了异常,则返回 [error, undefined]
。
注意,如果你没有现成的函数可以调用,也可以使用匿名函数与 tryCatch
一起使用。
console.log(
tryCatch(() => {
throw new Error("It threw");
})
);
处理异步函数
异步函数稍微复杂一些。我最初的想法是写一个完全异步的版本,可能叫做 asyncTryCatch
,但这没有什么挑战性。这是一个同时适用于异步和非异步函数的 tryCatch
实现:
function tryCatch(fn, ...args) {
try {
const result = fn.apply(null, args);
if (result.then) {
return new Promise((resolve) => {
result
.then((v) => resolve([undefined, v]))
.catch((e) => resolve([e, undefined]));
});
}
return [undefined, result];
} catch (e) {
return [e, undefined];
}
}
function throws() {
throw new Error("It threw");
}
async function asyncSum(first, second) {
return first + second;
}
async function asyncThrows() {
throw new Error("It throws async");
}
// 返回一个和
// 打印 [ undefined, 2 ]
console.log(tryCatch(Math.sqrt, 4));
// 返回一个错误
// 打印 [ Error: 'It threw', undefined ]
console.log(tryCatch(throws));
// 返回一个 promise 解析为值
// 打印 [ undefined, 3 ]
console.log(await tryCatch(asyncSum, 1, 2));
// 返回一个 promise 解析为错误
// 打印 [ Error: 'It throws async', undefined ]
console.log(await tryCatch(asyncThrows));
它看起来与原始版本非常相似,但加入了一些基于 Promise 的代码。通过这个实现,你可以在调用非异步函数时使用 tryCatch
,在调用异步函数时使用 await tryCatch
。
让我们看看 Promise 部分:
if (result.then) {
return new Promise((resolve) => {
result
.then((v) => resolve([undefined, v]))
.catch((e) => resolve([e, undefined]));
});
}
if (result.then)
检查给定函数(通过 apply
调用)是否返回了一个 Promise。如果是,我们需要返回一个 Promise。
调用 result.then(v => resolve([undefined, v]))
会导致 promise 解析为给定函数返回的值,如果没有抛出异常。
.catch(e => resolve([e, undefined]))
稍微复杂一些。我最初写成 .catch(e => reject([e, undefined]))
,但这会导致一个未捕获的错误从 tryCatch
中抛出。我们需要在这里解析,因为我们返回的是一个数组,而不是抛出一个错误。
最后
我经常需要使用 try/catch
,但觉得显式的 try/catch
块占用了大量空间,并且在作用域赋值时很烦人。我不确定我是否会使用它,但这确实是一个有趣的小探索。