当我开始(和我的团队)用 TypeScript 和 Svelte(我们都讨厌的 JavaScript 和 React)重写我们的应用程序时,我遇到了一个问题:
如何安全地输入 HTTP 响应的所有可能的正文?
这对你来说有启发吗?如果没有,你很可能就是“其中之一”,呵呵。让我们暂时离题一下,以便更好地理解图片。
为什么这个领域似乎未经探索
似乎没有人关心 HTTP 响应的“所有可能的主体”,因为我找不到为此做的任何东西(好吧,也许是 ts-fetch)。让我快速通过我的逻辑来解释为什么会这样。
没有人关心,因为人们:
只关心happy path:HTTP状态码为2xx时的响应体。
人们在其他地方手动输入它。
对于#1,我想说的是,开发人员(尤其是没有经验的开发人员)忘记了 HTTP 请求可能会失败,并且失败响应中携带的信息很可能与常规响应完全不同。
对于#2,让我们深入研究 ky 和 axios 等流行 NPM 包中发现的一个大问题。
数据获取包的问题
据我所知,人们喜欢像 ky 或 axios 这样的包,因为它们的“功能”之一是它们会在不正常的 HTTP 状态代码上抛出错误。从什么时候开始可以这样了?自从从来没有。但显然人们并没有意识到这一点。人们很高兴并且满足于在不正常的响应中遇到错误。
我想人们在捕捉的时候会输入不正常的身体。多么混乱,多么有代码味道!
这是一种代码味道,因为您有效地使用 try..catch 块作为分支语句,而 try..catch 并不意味着是分支语句。
但即使你与我争论分支在 try..catch 中自然发生,还有另一个导致这种情况仍然不好的重要原因:当抛出错误时,运行时需要展开调用堆栈。就 CPU 周期而言,这比使用 if 或 switch 语句的常规分支要昂贵得多。
知道了这一点,你能证明仅仅因为滥用 try..catch 块而造成的性能损失是合理的吗?我说不。我想不出有什么理由能让前端世界对此感到非常满意。
既然我已经解释了我的推理,那么让我们回到正题吧。
问题,详细
HTTP 响应可能会根据其状态代码携带不同的信息。例如,接收 PATCH HTTP 请求的 todo 端点(例如 api/todos/:id)在响应状态代码为 200 时可能会返回具有不同正文的响应,而响应状态代码为 400 时可能会返回不同正文的响应。
举个例子:
// For the 200 response, a copy of the updated object: { "id": 123, "text": "The updated text" } // For the 400 response, a list of validation errors: { "errors": [ "The updated text exceeds the maximum allowed number of characters." ] }
因此,考虑到这一点,我们回到问题陈述:如何键入一个执行此 PATCH 请求的函数,其中 TypeScript 可以告诉我正在处理哪个主体,具体取决于我编写的 HTTP 状态代码代码?答案:使用流式语法(构建器语法、链式语法)来累积类型。
构建解决方案
让我们首先定义一个基于先前类型构建的类型:
export type AccumType<t newt> = T | NewT; </t>
超级简单:给定类型 T 和 NewT,将它们连接起来形成一个新类型。在 AccumType 中再次使用这个新类型作为 T,然后就可以累积另一个新类型。然而,这手工完成的并不好。让我们介绍一下解决方案的另一个关键部分:流畅的语法。
流畅的语法
给定类 X 的一个对象,其方法始终返回其自身(或自身的副本),可以将方法调用一个接一个地链接起来。这是流畅的语法,或者链式语法。
让我们编写一个简单的类来执行此操作:
export class NamePending<t> { accumulate<newt>() { return this as NamePending<accumtype newt>>; } } // Now you can use it like this: const x = new NamePending(); // x is of type NamePending. const y = x.accumulate // y is of type NamePending. </accumtype></newt></t>
尤里卡!我们已经成功地将流畅的语法和我们编写的类型结合起来,开始将数据类型累积为单一类型!
如果不明显,您可以继续练习,直到积累了所需的类型(x.accumulate().accumulate()…直到完成)。
这一切都很好,但是这个超级简单的类型并没有将 HTTP 状态代码绑定到相应的正文类型。
完善我们所拥有的
我们想要的是为 TypeScript 提供足够的信息,以便其类型缩小功能发挥作用。为此,我们需要执行必要的操作来获取与原始问题相关的代码(在每个文件中键入 HTTP 响应的正文) -状态代码基础)。
首先,重命名并进化 AccumType。下面的代码显示了迭代的进展:
// Iteration 1. export type FetchResult<t newt> = T | NewT; // Iteration 2. export type FetchResponse<tstatus extends number tbody> = { ok: boolean; status: TStatus; statusText: string; body: TBody }; export type FetchResult<t tstatus extends number newt> = T | FetchResponse<tstatus newt>; //Makes sense to rename NewT to TBody. </tstatus></t></tstatus></t>
此时,我意识到:状态代码是有限的:我可以(并且确实)查找它们并为它们定义类型,并使用这些类型来限制类型参数 TStatus:
// Iteration 3. export type OkStatusCode = 200 | 201 | 202 | ...; export type ClientErrorStatusCode = 400 | 401 | 403 | ...; export type ServerErrorStatusCode = 500 | 501 | 502 | ...; export type StatusCode = OkStatusCode | ClientErrorStatusCode | ServerErrorStatusCode; export type NonOkStatusCode = Exclude<statuscode okstatuscode>; export type FetchResponse<tstatus extends statuscode tbody> = { ok: TStatus extends OkStatusCode ? true : false; status: TStatus; statusText: string; body: TBody }; export type FetchResult<t tstatus extends statuscode tbody> = T | FetchResponse<tstatus tbody>; </tstatus></t></tstatus></statuscode>
我们已经得到了一系列非常漂亮的类型:通过基于 ok 或 status 属性上的条件进行分支(编写 if 语句),TypeScript 的类型缩小功能将启动!不信,我们写一下class部分来试试:
export class DrFetch<t> { for<tstatus extends statuscode tbody>() { return this as DrFetch<fetchresult tstatus tbody>>; } } </fetchresult></tstatus></t>
试驾一下:
// For the 200 response, a copy of the updated object: { "id": 123, "text": "The updated text" } // For the 400 response, a list of validation errors: { "errors": [ "The updated text exceeds the maximum allowed number of characters." ] }
现在应该清楚为什么类型缩小能够根据 status 属性的 ok 属性正确预测分支时主体的形状。
但是,有一个问题:实例化类时的初始类型,在上面的注释块中标记。我是这样解决的:
export type AccumType<t newt> = T | NewT; </t>
这个小改变有效地排除了最初的输入,我们现在开始营业了!
现在我们可以编写如下代码,Intellisense 将 100% 准确:
export class NamePending<t> { accumulate<newt>() { return this as NamePending<accumtype newt>>; } } // Now you can use it like this: const x = new NamePending(); // x is of type NamePending. const y = x.accumulate // y is of type NamePending. </accumtype></newt></t>
在查询 ok 属性时,类型缩小也将起作用。
如果您没有注意到,我们可以通过不抛出错误来编写更好的代码。根据我的专业经验,axios 是错误的,ky 是错误的,任何其他 fetch helper 做同样的事情都是错误的。
结论
TypeScript 确实很有趣。通过结合 TypeScript 和 Fluent 语法,我们能够根据需要积累尽可能多的类型,这样我们就可以从第一天开始编写更准确、更清晰的代码,而不是一遍又一遍地调试。这项技术已被证明是成功的,并且可供任何人尝试。安装 dr-fetch 并测试它:
// Iteration 1. export type FetchResult<t newt> = T | NewT; // Iteration 2. export type FetchResponse<tstatus extends number tbody> = { ok: boolean; status: TStatus; statusText: string; body: TBody }; export type FetchResult<t tstatus extends number newt> = T | FetchResponse<tstatus newt>; //Makes sense to rename NewT to TBody. </tstatus></t></tstatus></t>
更复杂的包
我还创建了 wj-config,一个旨在完全消除过时的 .env 文件和 dotenv 的软件包。该包还使用此处教授的 TypeScript 技巧,但它使用 & 而不是 | 连接类型。如果您想尝试一下,请安装 v3.0.0-beta.1。不过,打字要复杂得多。在 wj-config 之后进行 dr-fetch 简直就是在公园里散步。
有趣的东西:那里有什么
让我们看看与 fetch 相关的包中的一些错误。
同构获取
您可以在自述文件中看到:
// Iteration 3. export type OkStatusCode = 200 | 201 | 202 | ...; export type ClientErrorStatusCode = 400 | 401 | 403 | ...; export type ServerErrorStatusCode = 500 | 501 | 502 | ...; export type StatusCode = OkStatusCode | ClientErrorStatusCode | ServerErrorStatusCode; export type NonOkStatusCode = Exclude<statuscode okstatuscode>; export type FetchResponse<tstatus extends statuscode tbody> = { ok: TStatus extends OkStatusCode ? true : false; status: TStatus; statusText: string; body: TBody }; export type FetchResult<t tstatus extends statuscode tbody> = T | FetchResponse<tstatus tbody>; </tstatus></t></tstatus></statuscode>
“服务器响应错误”??没有。 “服务器说你的请求不好”。是的,投掷部分本身就很糟糕。
ts 获取
这个想法是正确的,但不幸的是只能输入 OK 与非 OK 响应(最多 2 种类型)。
凯
我最批评的软件包之一,展示了这个例子:
export class DrFetch<t> { for<tstatus extends statuscode tbody>() { return this as DrFetch<fetchresult tstatus tbody>>; } } </fetchresult></tstatus></t>
这是一个非常初级的开发人员会写的:只是快乐的道路。根据其自述文件,等效性:
const x = new DrFetch(); // Ok, having to write an empty type is inconvenient. const y = x .for() .for() ; /* y's type: DrFetch */
投掷部分太糟糕了:为什么要分支到投掷,强迫你稍后接住?这对我来说毫无意义。错误中的文本也具有误导性:它不是“获取错误”。抓取成功了。你得到了回应,不是吗?你只是不喜欢它……因为这不是快乐的道路。更好的措辞是“HTTP 请求失败:”。失败的是请求本身,而不是获取操作。
以上是如何使用 TypeScript 累积类型:输入所有可能的 fetch() 结果的详细内容。更多信息请关注PHP中文网其他相关文章!

是的,JavaScript的引擎核心是用C语言编写的。1)C语言提供了高效性能和底层控制,适合JavaScript引擎的开发。2)以V8引擎为例,其核心用C 编写,结合了C的效率和面向对象特性。3)JavaScript引擎的工作原理包括解析、编译和执行,C语言在这些过程中发挥关键作用。

JavaScript是现代网站的核心,因为它增强了网页的交互性和动态性。1)它允许在不刷新页面的情况下改变内容,2)通过DOMAPI操作网页,3)支持复杂的交互效果如动画和拖放,4)优化性能和最佳实践提高用户体验。

C 和JavaScript通过WebAssembly实现互操作性。1)C 代码编译成WebAssembly模块,引入到JavaScript环境中,增强计算能力。2)在游戏开发中,C 处理物理引擎和图形渲染,JavaScript负责游戏逻辑和用户界面。

JavaScript在网站、移动应用、桌面应用和服务器端编程中均有广泛应用。1)在网站开发中,JavaScript与HTML、CSS一起操作DOM,实现动态效果,并支持如jQuery、React等框架。2)通过ReactNative和Ionic,JavaScript用于开发跨平台移动应用。3)Electron框架使JavaScript能构建桌面应用。4)Node.js让JavaScript在服务器端运行,支持高并发请求。

Python更适合数据科学和自动化,JavaScript更适合前端和全栈开发。1.Python在数据科学和机器学习中表现出色,使用NumPy、Pandas等库进行数据处理和建模。2.Python在自动化和脚本编写方面简洁高效。3.JavaScript在前端开发中不可或缺,用于构建动态网页和单页面应用。4.JavaScript通过Node.js在后端开发中发挥作用,支持全栈开发。

C和C 在JavaScript引擎中扮演了至关重要的角色,主要用于实现解释器和JIT编译器。 1)C 用于解析JavaScript源码并生成抽象语法树。 2)C 负责生成和执行字节码。 3)C 实现JIT编译器,在运行时优化和编译热点代码,显着提高JavaScript的执行效率。

JavaScript在现实世界中的应用包括前端和后端开发。1)通过构建TODO列表应用展示前端应用,涉及DOM操作和事件处理。2)通过Node.js和Express构建RESTfulAPI展示后端应用。

JavaScript在Web开发中的主要用途包括客户端交互、表单验证和异步通信。1)通过DOM操作实现动态内容更新和用户交互;2)在用户提交数据前进行客户端验证,提高用户体验;3)通过AJAX技术实现与服务器的无刷新通信。


热AI工具

Undresser.AI Undress
人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover
用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool
免费脱衣服图片

Clothoff.io
AI脱衣机

Video Face Swap
使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热门文章

热工具

SublimeText3汉化版
中文版,非常好用

适用于 Eclipse 的 SAP NetWeaver 服务器适配器
将Eclipse与SAP NetWeaver应用服务器集成。

WebStorm Mac版
好用的JavaScript开发工具

SublimeText3 Linux新版
SublimeText3 Linux最新版

MinGW - 适用于 Windows 的极简 GNU
这个项目正在迁移到osdn.net/projects/mingw的过程中,你可以继续在那里关注我们。MinGW:GNU编译器集合(GCC)的本地Windows移植版本,可自由分发的导入库和用于构建本地Windows应用程序的头文件;包括对MSVC运行时的扩展,以支持C99功能。MinGW的所有软件都可以在64位Windows平台上运行。