防止错误阻止你:安全赋值运算符 (?=
) 和优雅处理承诺
随着JavaScript的发展,新的特性和提案不断涌现,旨在提高编码效率并减少错误。其中一个特性是安全赋值运算符(?=
),这是语言的一个提议新增功能。虽然我们仍在等待其正式发布,但我们今天可以实现类似的功能,以保护我们的代码免受诸如null
或undefined
值等常见问题的影响。
在本文中,我们将探讨 ?=
操作符,使用现有的JavaScript构建自己的版本,并介绍在异步操作中更优雅地处理承诺的实用方法。
了解安全赋值操作符 (?=
)
?=
操作符是什么?
安全赋值操作符 (?=
) 允许开发人员仅在目标为 null
或 undefined
时将值赋给变量。这是一种更简洁的方式来表达,“如果变量为空,则赋值。”
这里的工作原理是:
let 用户名 = null;
用户名 ?= "Shahar";
console.log(用户名); // 输出: "Shahar"
进入全屏模式 退出全屏模式
在这种情况下,变量 username
被赋值为 "Shahar"
,因为它的值原来是 null
。如果 username
已经有值了,该操作符就会跳过赋值。
为什么它很有用
?=
运算符通过减少显式的if
检查或三元运算符的使用,来简化代码,从而确保安全赋值。然而,此运算符在ECMAScript中仍处于提案阶段,这意味着在成为JavaScript语言的一部分之前它可能会发生变化。你可以在这里跟踪其发展情况:这里。
构建一个安全赋值函数
推出 safeAssign
在等待 ?=
成为官方标准的同时,我们可以通过一个自定义的辅助函数 safeAssign
来模仿它的行为。这个函数使用了空值合并运算符 (??
),该运算符已经在现代环境中得到了广泛支持。
这里是我们的 safeAssign
函数:
function safeAssign(target, value) {
return target ?? value;
}
进入全屏模式 退出全屏模式
示例演示
让我们看看它是如何工作的:
let username = undefined;
username = safeAssign(username, "Shahar");
console.log(username); // 输出: "Shahar"
进入全屏模式 退出全屏模式
这实际上就是 ?=
操作符的作用。如果变量为 null
或 undefined
,我们就给它赋值;否则,我们就保持原样不作改动。
safeAssign
的限制
虽然 safeAssign
提供了与 ?=
类似的功能,但它存在一些限制:
- 简洁性 :
safeAssign
是一个工具函数,无法提供与原生?=
操作符相同的语法优雅程度。过度使用自定义函数可能导致代码更加冗长。 - 性能 : 虽然在小型应用中
safeAssign
的性能影响可以忽略不计,但在大型系统中,由于引擎优化,原生操作符如?=
可能会更快。 - 浏览器支持 :
safeAssign
中使用的空值合并操作符 (??
) 在大多数现代浏览器和环境中得到支持,但在较旧的环境中可能需要使用 polyfills 才能支持。
与其他语言的快速比较
许多其他语言提供了与提议的 ?=
操作符类似的功能:
- C# 有一个空合并赋值运算符 (
??=
),其行为类似于 JavaScript 的?=
提案。 - Python 使用
or
关键字进行安全赋值,其中a = a or value
是一种常见的模式,用于仅在a
为假值时赋值。
这些操作符使得处理可能为空的值更加直接,减少了冗余代码。
使用 safeAwait
处理异步操作
介绍 safeAwait
在使用JavaScript进行异步操作时,很容易遇到被拒绝的Promise或意外的结果。与其使用.catch()
手动处理每个拒绝,我们可以使用一个名为safeAwait
的自定义函数来简化这一过程,该函数将Promise包裹在一个更干净、更安全的结构中。
这里是 safeAwait
函数:
async function safeAwait(promise, errorHandler) {
try {
const data = await promise;
return [null, data]; // 成功:没有错误,返回数据
} catch (error) {
if (errorHandler) errorHandler(error); // 可选错误处理程序
return [error, null]; // 发生错误,返回错误和空数据
}
}
进入全屏模式 退出全屏模式
示例:带有错误处理的数据获取
让我们使用 safeAwait
从API获取数据并处理可能出现的错误:
async function 获取数据() {
const [错误, 响应] = await safeAwait(
fetch("https://api.example.com"),
(err) => console.error("请求失败:", err)
);
if (错误) return; // 如果有错误则退出
return 响应; // 如果成功则返回响应
}
进入全屏模式 退出全屏模式
在这个例子中,safeAwait
处理了成功和错误两种情况,使得调用函数能够以更可预测的方式处理结果。
safeAwait
的变体
我们也可以为 safeAwait
扩展不同的用例。例如,这里有一个版本会在失败前重试一次承诺:
async function safeAwaitWithRetry(promise, errorHandler, retries = 1) {
let attempt = 0;
while (attempt <= retries) {
const [error, data] = await safeAwait(promise, errorHandler);
if (!error) return [null, data];
attempt++;
}
return [new Error("达到最大重试次数"), null];
}
进入全屏模式 退出全屏模式
这种变体在放弃之前会重试指定次数。
JavaScript 中的最佳错误处理实践
当处理异步代码时,正确的错误处理至关重要。这里有一些最佳实践:
- 始终处理被拒绝的承诺:未处理的被拒绝承诺可能导致崩溃或未定义的行为。使用
try/catch
或.catch()
确保承诺得到妥善处理。 - 集中错误处理:使用
safeAwait
这样的工具函数可以让你集中处理错误,使管理和调试代码变得更加容易。 - 优雅降级:确保你的应用程序能够优雅地从错误中恢复,而不会崩溃或让用户处于未定义的状态。
- 使用自定义错误消息:在抛出错误时,提供有意义的错误消息以帮助调试。
使用 safeAssign
和 safeAwait
之前的和之后的代码对比
这里快速比较了这些工具如何清理你的代码。
未使用 safeAssign
:
如果 (user === null || user === undefined) {
user = "Shahar";
}
进入全屏模式 退出全屏模式
使用 safeAssign
:
user = safeAssign(user, "Shahar");
进入全屏模式 退出全屏模式
未使用 safeAwait
:
try {
const response = await fetch("https://api.example.com");
} catch (error) {
console.error("请求失败:", error);
}
进入全屏模式 退出全屏模式
使用 safeAwait
:
const [error, response] = await safeAwait(fetch("https://api.example.com"), (err) => console.error("请求失败:", err));
进入全屏模式 退出全屏模式
结论
总之,虽然安全赋值运算符 (?=
) 仍然是一个提案,但我们今天可以使用 safeAssign
函数为 nullish 值赋值,并使用 safeAwait
处理更复杂的异步操作。这两个工具简化了你的代码,使其更易读和更易维护。
主要收获:
?=
操作符简化了安全赋值,但目前仍处于提案阶段。- 可以使用空值合并操作符 (
??
) 来通过safeAssign
复现?=
的功能,该操作符已被广泛支持。 - 对于异步操作,
safeAwait
提供了一种更干净的方式来处理 promise 的拒绝和错误。 - 关注 ECMAScript 提案 以获取未来的更新。
通过利用这些模式,你可以像专业人士一样处理错误,并保持代码整洁、易读且安全。