搜索
首页web前端js教程如何使用 TypeScript 累积类型:输入所有可能的 fetch() 结果

How to Use TypeScript to Accumulate Types: Typing ALL possible fetch() Results

当我开始(和我的团队)用 TypeScript 和 Svelte(我们都讨厌的 JavaScript 和 React)重写我们的应用程序时,我遇到了一个问题:

如何安全地输入 HTTP 响应的所有可能的正文?

这对你来说有启发吗?如果没有,你很可能就是“其中之一”,呵呵。让我们暂时离题一下,以便更好地理解图片。

为什么这个领域似乎未经探索

似乎没有人关心 HTTP 响应的“所有可能的主体”,因为我找不到为此做的任何东西(好吧,也许是 ts-fetch)。让我快速通过我的逻辑来解释为什么会这样。

没有人关心,因为人们:

  1. 只关心happy path:HTTP状态码为2xx时的响应体。

  2. 人们在其他地方手动输入它。

对于#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中文网其他相关文章!

声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
JavaScript是用C编写的吗?检查证据JavaScript是用C编写的吗?检查证据Apr 25, 2025 am 12:15 AM

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

JavaScript的角色:使网络交互和动态JavaScript的角色:使网络交互和动态Apr 24, 2025 am 12:12 AM

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

C和JavaScript:连接解释C和JavaScript:连接解释Apr 23, 2025 am 12:07 AM

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

从网站到应用程序:JavaScript的不同应用从网站到应用程序:JavaScript的不同应用Apr 22, 2025 am 12:02 AM

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

Python vs. JavaScript:比较用例和应用程序Python vs. JavaScript:比较用例和应用程序Apr 21, 2025 am 12:01 AM

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

C/C在JavaScript口译员和编译器中的作用C/C在JavaScript口译员和编译器中的作用Apr 20, 2025 am 12:01 AM

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

JavaScript在行动中:现实世界中的示例和项目JavaScript在行动中:现实世界中的示例和项目Apr 19, 2025 am 12:13 AM

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

JavaScript和Web:核心功能和用例JavaScript和Web:核心功能和用例Apr 18, 2025 am 12:19 AM

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

See all articles

热AI工具

Undresser.AI Undress

Undresser.AI Undress

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

AI Clothes Remover

AI Clothes Remover

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

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

Video Face Swap

Video Face Swap

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

热工具

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

适用于 Eclipse 的 SAP NetWeaver 服务器适配器

适用于 Eclipse 的 SAP NetWeaver 服务器适配器

将Eclipse与SAP NetWeaver应用服务器集成。

WebStorm Mac版

WebStorm Mac版

好用的JavaScript开发工具

SublimeText3 Linux新版

SublimeText3 Linux新版

SublimeText3 Linux最新版

MinGW - 适用于 Windows 的极简 GNU

MinGW - 适用于 Windows 的极简 GNU

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