JS、Go、Rust 错误处理的不同 - JS 可以不用 Try/Catch 吗?

在这里插入图片描述

原文:Mateusz Piorowski - 2023.07.24

先来了解一下我的背景吧。我是一名软件开发人员,有大约十年的工作经验,最初使用 PHP,后来逐渐转向 JavaScript。

大约五年前,我开始使用 TypeScript,从那时起,我就再也没有使用过 JavaScript。从开始使用 TypeScript 的那一刻起,我就认为它是有史以来最好的编程语言。每个人都喜欢它,每个人都在使用它……它就是最好的,对吗?对吧?对不对?

是的,然后我开始接触其他的语言,更现代化的语言。首先是 Go,然后我慢慢地把 Rust 也加了进来。

当你不知道存在不同的事物时,就很难错过它们。

我在说什么?Go 和 Rust 的共同点是什么?Error,这是最让我印象深刻的一点。更具体地说,这些语言是如何处理错误的。

JavaScript 依靠抛出异常来处理错误,而 Go 和 Rust 则将错误视为值。你可能会觉得这没什么大不了的…但是,好家伙,这听起来似乎微不足道;然而,它却改变了游戏规则。

让我们来了解一下它们。我们不会深入研究每种语言,只是想了解一般的处理方式。

让我们从 JavaScript/TypeScript 和一个小游戏开始。

给自己五秒钟的时间来查看下面的代码,并回答为什么我们需要用 try/catch 来包裹它。

try {const request = { name: “test”, value: 2n };const body = JSON.stringify(request);const response = await fetch("https://example.com", {method:POST,body,});if (!response.ok) {return;}// 处理响应
} catch (e) {// 处理错误return;
}

那么,我想你们大多数人都猜到了,尽管我们检查了 response.ok,但 fetch 方法仍然可能抛出一个异常。response.ok 只能“捕获” 4xx 和 5xx 的网络错误。但是,当网络本身失败时,它会抛出一个异常。

但我不知道有多少人猜到 JSON.stringify 也会抛出一个异常。原因是请求对象包含 bigint (2n) 变量,而 JSON 不知道如何将其序列化为字符串。

所以,第一个问题是,我个人认为这是 JavaScript 最大的问题:我们不知道什么可能会抛出一个异常。从 JavaScript 错误的角度来看,它与下面的情况是一样的:

try {let data = “Hello”;
} catch (err) {console.error(err);
}

JavaScript 不知道;JavaScript 也不在乎。你应该知道。

第二个问题,这是完全可行的代码:

const request = { name: “test”, value: 2n };
const body = JSON.stringify(request);
const response = await fetch("https://example.com", {method:POST,body,
});
if (!response.ok) {return;
}

没有错误,没有语法检查,尽管这可能会导致你的应用程序崩溃。

现在,在我的脑海中,我听到的是:“有什么问题,在任何地方使用 try/catch 就可以了”。这就引出了第三个问题:我们不知道哪个异常被抛出。当然,我们可以通过错误信息来猜测,但对于规模较大、可能发生错误的地方较多的服务/函数来说,又该怎么办呢?你确定用一个 try/catch 就能正确处理所有错误吗?

好了,是时候停止对 JS 的挑剔,转而讨论其他问题了。让我们从这段 Go 代码开始:

f, err := os.Open(“filename.ext”)
if err != nil {log.Fatal(err)
}
// 对打开的 *File f 进行一些操作

我们正在尝试打开一个返回文件或错误的文件。你会经常看到这种情况,主要是因为我们知道哪些函数总是返回错误,你绝不会错过任何一个。这是第一个将错误视为值的例子。你可以指定哪个函数可以返回错误值,然后返回错误值,分配错误值,检查错误值,处理错误值。

这也是 Go 被诟病的地方之一——“错误检查代码”,其中 if err != nil { … 有时候的代码行数比其他部分还要多。

if err != nil {if err != nil {if err != nil {}}
}
if err != nil {}if err != nil {}

尽管如此,相信我,这些努力还是值得的。

最后,让我们看看 Rust:

let greeting_file_result = File::open(“hello.txt”);
let greeting_file = match greeting_file_result {Ok(file) => file,Err(error) => panic!("Problem opening the file: {:?}", error),
};

这里显示的是三种错误处理中最冗长的一种,具有讽刺意味的是,它也是最好的一种。首先,Rust 使用其神奇的枚举(它们与 TypeScript 的枚举不同!)来处理错误。这里无需赘述,重要的是它使用了一个名为 Result 的枚举,有两个变量:OkErr。你可能已经猜到,Ok 包含一个值,而 Err 包含……没错,一个错误 😄。

它也有很多更方便的处理方式来缓解 Go 的问题。最知名的一个是 ? 操作符。

let greeting_file_result = File::open(“hello.txt")?;

这里的总结是,Go 和 Rust 总是知道哪里可能会出错。它们强迫你在错误出现的地方(大部分情况下)立即处理它。没有隐藏的错误,不需要猜测,也不会因为意外的错误而导致应用程序崩溃。

而这种方法就是更好,好得多。

好了,是时候实话实说了;我撒了点小谎。我们无法让 TypeScript 的错误像 Go / Rust 那样工作。限制因素在于语言本身,它没有合适的工具来做到这一点。

但我们能做的就是尽量使其相似。并且让它变得简单。

从这里开始:

export type Safe<T> =| {success: true;data: T;}| {success: false;error: string;};

这里没有什么花哨的东西,只是一个简单的通用类型。但这个小东西却能彻底改变代码。你可能会注意到,这里最大的不同就是我们要么返回数据,要么返回错误。听起来熟悉吗?

另外…第二个谎言是,我们确实需要一些 try/catch。好在我们只需要两个,而不是十万个。

export function safe<T>(promise: Promise<T>, err?: string): Promise<Safe<T>>;
export function safe<T>(func: () => T, err?: string): Safe<T>;
export function safe<T>(promiseOrFunc: Promise<T> | (() => T),err?: string
): Promise<Safe<T>> | Safe<T> {if (promiseOrFunc instanceof Promise) {return safeAsync(promiseOrFunc, err);}return safeSync(promiseOrFunc, err);
}async function safeAsync<T>(promise: Promise<T>,err?: string
): Promise<Safe<T>> {try {const data = await promise;return { data, success: true };} catch (e) {console.error(e);if (err !== undefined) {return { success: false, error: err };}if (e instanceof Error) {return { success: false, error: e.message };}return { success: false, error: "Something went wrong" };}
}function safeSync<T>(func: () => T, err?: string): Safe<T> {try {const data = func();return { data, success: true };} catch (e) {console.error(e);if (err !== undefined) {return { success: false, error: err };}if (e instanceof Error) {return { success: false, error: e.message };}return { success: false, error: "Something went wrong" };}
}

“哇,真是个天才。他为 try/catch 创建了一个包装器。” 是的,你说得没错;这只是一个包装器,我们的 Safe 类型作为返回类型。但有时候,简单的东西就是你所需要的。让我们将它们与上面的例子结合起来。

旧的(16 行)示例:

try {const request = { name: “test”, value: 2n };const body = JSON.stringify(request);const response = await fetch("https://example.com", {method:POST,body,});if (!response.ok) {// 处理网络错误return;}// 处理响应
} catch (e) {// 处理错误return;
}

新的(20 行)示例:

const request = { name: “test”, value: 2n };
const body = safe(() => JSON.stringify(request),“Failed to serialize request”,
);
if (!body.success) {// 处理错误(body.error)return;
}
const response = await safe(fetch("https://example.com", {method:POST,body: body.data,}),
);
if (!response.success) {// 处理错误(response.error)return;
}
if (!response.data.ok) {// 处理网络错误return;
}
// 处理响应(body.data)

是的,我们的新解决方案更长,但性能更好,原因如下:

  • 没有 try/catch
  • 我们在错误发生的地方处理每个错误
  • 我们可以为特定函数指定一个错误信息
  • 我们有一个很好的自上而下的逻辑,所有错误都在顶部,然后底部只有响应

但现在王牌来了,如果我们忘记检查这个:

if (!body.success) {// 处理错误 (body.error)return;
}

事实是……我们不能忘记。是的,我们必须进行这个检查。如果我们不这样做,body.data 将不存在。LSP 会通过抛出 “Property ‘data’ does not exist on type ‘Safe’” 错误来提醒我们。这都要归功于我们创建的简单的 Safe 类型。它同样适用于错误信息,我们在检查 !body.success 之前无法访问 body.error

这是我们应该欣赏 TypeScript 以及它如何改变 JavaScript 世界的时刻。

以下也同样适用:

if (!response.success) {// 处理错误 (response.error)return;
}

我们不能移除 !response.success 检查,否则,response.data 将不存在。

当然,我们的解决方案也不是没有问题。最大的问题是你必须记住要用我们的 safe 包装器包装可能抛出异常的 Promise/函数。这个 “我们需要知道” 是我们无法克服的语言限制。

这听起来很难,但其实并不难。你很快就会意识到,你代码中的几乎所有 Promises 都会出错,而那些会出错的同步函数你也知道,而且它们的数量并不多。

不过,你可能会问,这样做值得吗?我们认为值得,而且在我们团队中运行得非常好:)。当你看到一个更大的服务文件,没有任何 try/catch,每个错误都在出现的地方得到了处理,逻辑流畅…它看起来就很不错。

这是一个使用 SvelteKit FormAction 的真实例子:

export const actions = {createEmail: async ({ locals, request }) => {const end = perf(“CreateEmail”);const form = await safe(request.formData());if (!form.success) {return fail(400, { error: form.error });}const schema = z.object({emailTo: z.string().email(),emailName: z.string().min(1),emailSubject: z.string().min(1),emailHtml: z.string().min(1),}).safeParse({emailTo: form.data.get("emailTo"),emailName: form.data.get("emailName"),emailSubject: form.data.get("emailSubject"),emailHtml: form.data.get("emailHtml"),});if (!schema.success) {console.error(schema.error.flatten());return fail(400, { form: schema.error.flatten().fieldErrors });}const metadata = createMetadata(URI_GRPC, locals.user.key)if (!metadata.success) {return fail(400, { error: metadata.error });}const response = await new Promise<Safe<Email__Output>>((res) => {usersClient.createEmail(schema.data, metadata.data, grpcSafe(res));});if (!response.success) {return fail(400, { error: response.error });}end();return {email: response.data,};},
} satisfies Actions;

这里有几点需要指出:

  • 我们的自定义函数 grpcSafe 可以帮助我们处理 gGRPC 回调。
  • createMetadata 内部返回 Safe,因此我们不需要对其进行封装。
  • zod 库使用相同的模式 😃 如果我们不进行 schema.success 检查,我们就无法访问 schema.data

看起来是不是很简洁?那就试试吧!也许它也非常适合你 😃

感谢阅读。

附注:下面的代码对比是不是看起来很像?

f, err := os.Open(“filename.ext”)
if err != nil {log.Fatal(err)
}
// 使用打开的 *File f 做一些事情
const response = await safe(fetch(“https://example.com"));
if (!response.success) {console.error(response.error);return;
}
// 使用 response.data 做一些事情

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/14162.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Flume 的基本介绍和安装部署

一、Flume 概述 Flume 是 Cloudera 提供的一个高可用的&#xff0c;高可靠的&#xff0c;分布式的海量日志采集、聚合和传输的框架服务 Flume 基于流式架构&#xff0c;灵活简单&#xff0c;能够实时读取服务器本地磁盘的数据&#xff0c;将数据写入到 HDFS 二、Flume 基础架构…

Cloneable 接口和深拷贝,浅拷贝

目录 一.Cloneable 接口 二.浅拷贝 三.深拷贝 四.comparable接口、 五.comparator接口 1.Java 中内置了一些很有用的接口 , Cloneable 就是其中之一 . Object 类中存在一个 clone 方法 , 调用这个方法可以创建一个对象的 " 拷贝 ". 2.来说说调用 clone 方法…

基于深度学习的表情识别系统

欢迎大家点赞、收藏、关注、评论啦 &#xff0c;由于篇幅有限&#xff0c;只展示了部分核心代码。 文章目录 一项目简介 二、功能三、系统四. 总结 一项目简介 一、项目背景 随着人工智能技术的快速发展&#xff0c;表情识别成为了人机交互领域的一个研究热点。表情识别技术旨…

Python数字比大小获取大的数

目录 一、引言 二、数字比较的基本语法 三、获取较大的数 使用条件语句 使用内置函数 四、处理特殊情况 比较非数字类型 处理无穷大和NaN 五、应用实例 在游戏开发中比较分数 在数据分析中找出最大值 六、优化与性能 七、总结 一、引言 在Python编程的广阔天地中…

巧秒用AI写作工具做影视解说文案,效率高!

在自媒体内容输出的快节奏当下&#xff0c;影视解说已经成为一种受欢迎的内容形式。然而&#xff0c;创作高质量的影视解说文案往往需要花费大量的时间和精力。随着人工智能技术的不断发展&#xff0c;AI写作工具为我们提供了一种全新的、高效的解决方案。 AI写作工具利用先进的…

AI服务器 IO互联芯片解决方案pcie switch国产替代博通

服务器是大数据、人工智能、区块链、云计算、元宇宙等的基础设施&#xff0c;全国每年400万台服务器出货&#xff0c;预计 2025年超过500万台&#xff08;中商产业研究院&#xff09;&#xff0c;高性能企业级互联芯片控制着服务器的神经系统和循环系统。 市场痛点&#xff1…

大厂程序员离职,开发一个盲盒小程序2万,一周开发完!

大家好&#xff0c;我是程序员小孟&#xff01; 前面接了一个盲盒的小程序&#xff0c;主要的还是商城&#xff0c;盲盒的话只是其中的有一个活动。 现在的年轻人是真的会玩&#xff0c;越来越新的东西出来&#xff0c;越来越好玩的东西流行。 就像最近很火的地摊盲盒。 讲…

第N4周:中文文本分类——Pytorch实现

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 | 接辅导、项目定制 数据集&#xff1a;train 一.加载数据 import torch import torch.nn as nn import torchvision from torchvision import transforms,d…

vue3的核心API功能:computed()API使用

常规使用方法: 这样是常规使用方法. 另一种使用方法: 这样分别定义computed的get回调函数和set回调函数, 上面例子定义了plusOne.value的值为1, 那么这时候就走了computed的set回调函数,而没有走get回调函数. 当我们打印plusOne.value的值的时候,走的是get的回调函数而不是…

ios 原生项目迁移flutter第一天环境

由于公司已经有第一个吃螃蟹的项目组&#xff0c;我在迁移的时候想着站在巨人的肩膀上&#xff0c;但是搭配环境一定要问清楚对方flutter版本&#xff0c;路径也要安排好&#xff0c;不然就不行。 对着自己的项目照着葫芦画瓢&#xff0c;我刚开始为了配置管理图个方便随便放&…

Unity3D读取Excel表格写入Excel表格

系列文章目录 unity工具 文章目录 系列文章目录&#x1f449;前言&#x1f449;一、读取Excel表格&#x1f449;二、写入Excel表格&#x1f449;三、Fileinfo和Directoryinfo的操作&#x1f449;四、壁纸分享&#x1f449;总结 &#x1f449;前言 有时候难免会遇到读取文件写…

提供一个c# winform的多语言框架源码,采用json格式作为语言包,使用简单易于管理加载且不卡UI,支持“语言分级”管理

提供一个c# winform的多语言框架源码&#xff0c;采用json格式作为语言包&#xff0c;不使用resx资源&#xff0c;当然本质一样的&#xff0c;你也可以改为resx 一、先看下测试界面 演示了基本的功能&#xff1a;切换语言&#xff0c;如何加载语言&#xff0c;如何分级加载语…

【webrtc】内置opus解码器的移植

m98 ,不知道是什么版本的opus,之前的交叉编译构建: 【mia】ffmpeg + opus 交叉编译 【mia】ubuntu22.04 : mingw:编译ffmpeg支持opus编解码 看起来是opus是1.3.1 只需要移植libopus和opus的webrtc解码部分即可。 linux构建的windows可运行的opus库 G:\NDDEV\aliply-0.4\C…

如何为社交feed场景设计缓存体系?no.35

Feed 流场景分析 Feed 流是很多移动互联网系统的重要一环&#xff0c;如微博、微信朋友圈、QQ 好友动态、头条/抖音信息流等。虽然这些产品形态各不相同&#xff0c;但业务处理逻辑却大体相同。用户日常的“刷刷刷”&#xff0c;就是在获取 Feed 流&#xff0c;这也是 Feed 流的…

达梦数据库详解

达梦认证是指针对中国数据库管理系统&#xff08;DBMS&#xff09;厂商达梦公司所推出的数据库产品&#xff0c;即达梦数据库&#xff08;DMDB&#xff09;&#xff0c;进行的一种官方认证体系。达梦认证旨在验证数据库管理人员对达梦数据库产品的掌握程度&#xff0c;及其在数…

【HUST】信道编码|基于LDPC码的物理层安全编码方案概述

本文对方案的总结是靠 Kimi 阅读相关论文后生成的&#xff0c;我只看了标题和摘要感觉确实是这么回事&#xff0c;并没有阅读原文。 行文逻辑&#xff1a;是我自己设定的&#xff0c;但我并不是这个研究领域的&#xff0c;所以如果章节划分时有问题&#xff0c;期待指出&#x…

FTP文件传输议

FTP是一种文件传输协议&#xff1a;用来上传和下载&#xff0c;实现远程共享文件&#xff0c;和统一管理文件 工作原理&#xff1a;用于互联网上的控制文件的双向传输是一个应用程序。工作在TCP/IP协议簇的&#xff0c;其传输协议是TCP协议提高文件传输的共享性和可靠性&#…

8.STL中Vector容器的常见操作(附习题)

目录 1.vector的介绍 2 vector的使用 2.1 vector的定义 2.2 vector iterator 的使用 2.3 vector 空间增长问题 2.3 vector 增删查改 2.4 vector 迭代器失效问题 2.5 vector 在OJ中的使用 1.vector的介绍 vector是表示可变大小数组的序列容器。 就像数组一样&#xff0…

【Unitydemo制作】音游制作—控制器与特效

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 秩沅 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a;就业…

儿童卧室灯品牌该如何挑选?几款专业儿童卧室灯品牌分享

近视在儿童中愈发普遍&#xff0c;许多家长开始认识到&#xff0c;除了学业成绩之外&#xff0c;孩子的视力健康同样重要。毕竟&#xff0c;学业的落后可以逐渐弥补&#xff0c;而一旦孩子近视&#xff0c;眼镜便可能成为长期伴随。因此&#xff0c;专业的护眼台灯对于每个家庭…