终于有人修复了 Javascript

2025-06-04

终于有人修复了 Javascript

JavaScript 生态系统发展飞速。就在你逐渐熟悉某种技术时,大量新的方法论应运而生。有些方法,例如 TypeScript,得到了广泛的采用,而另一些方法,例如 CoffeeScript,则悄然消亡。每一项创新最初都会激起人们的热情,但随着时间的推移,社区往往会出现分裂,反对者最终会衍生出自己的框架。这种无休止的循环让我对那些声称能解决所有问题的最新“神奇”框架越来越警惕。我已经从寻求工具作为解决方案,转变为拥抱对模式的理解,而不是不断追求新技术。

这就是为什么我要向您指出一些适合您的 TypeScript 项目的特殊的东西,它不仅仅是另一个工具,而是一个鼓励良好实践的范例:Effect

让我们来看看为什么你应该采取这一举措。

彩色函数

您是否曾问过自己,您的职能是什么颜色的?

让我来总结一下。假设你的代码库中有蓝色和红色的函数。规则很简单:你可以在蓝色函数中使用红色函数,但反过来不行。那岂不是一场噩梦?现在把蓝色替换成“async”。没错,JavaScript 中就有函数着色了。

那么,我们该如何应对这种颜色问题呢?如果我们想移除有颜色的函数,就需要创建某种包装器,只在需要时使用 Promise。比如“Future”或者……“Effect”?

import { Effect, pipe } from "effect";

const fibonacci = (a: number): Effect.Effect<number> =>
  a <= 1
    ? Effect.succeed(a)
    : pipe(
        Effect.all([fibonacci(a - 1), fibonacci(a - 2)]),
        Effect.map(([a, b]) => a + b)
      );

await Effect.runPromise(fibonacci(10));
Enter fullscreen mode Exit fullscreen mode

Effect使用而不是 的关键区别Promise在于如何处理并发。Effect 提供了 Fiber,这是一种类似于绿色线程或 goroutine 的轻量级并发结构。此功能使我们能够执行长时间运行或异步任务,而不会阻塞主线程,即使在传统的同步函数中也可以启动这些任务。

import { Effect, Console, pipe } from "effect";

const longRunningTask = pipe(
  Console.log("Start of long running task"),
  Effect.delay(1000),
  Effect.tap(Console.log("End of long running task"))
);

console.log("Start of program");
Effect.runFork(longRunningTask);
console.log("End of program");

/**
 * OUTPUT:
 * Start of program
 * End of program
 * Start of long running task
 * End of long running task
 */
Enter fullscreen mode Exit fullscreen mode

虽然 Effect 不会消除 JavaScript 中固有的异步/同步区别(函数着色),但通过使用纤程来处理异步操作,它允许同步函数调用异步效果,而不会使其自身变为异步,从而在很大程度上缓解“着色”问题。

类型安全错误

我们来看看这个函数:

const divide = (a: number, b: number) => a / b;
Enter fullscreen mode Exit fullscreen mode

我们刚刚引入了一个问题,我们不能除以零。所以让我们稍微重构一下代码:

const divide = (a: number, b: number) => {
  if (b === 0) throw new Error('Cannot divide by zero.');
  return a / b;
}
Enter fullscreen mode Exit fullscreen mode

看起来不错吧?其实不然。因为它不是类型安全的。想要使用你的函数的人根本不知道你的函数会抛出错误。对于像这样的简单函数来说,这看起来可能没什么,但当你有几十个潜在错误时,它就变成了一场噩梦。其他更成熟的语言有类似Either或的概念Result来表示类型安全的错误。它看起来像这样:

type Result<T, E> = Ok<T> | Err<E>;

// With something like:
type Ok<T> = { kind: "Ok", data: T };
type Err<E> = { kind: "Err", err: E };
Enter fullscreen mode Exit fullscreen mode

使用 Effect,您可以立即获得:Effect<T, E>。您无需再考虑运行过程中可能发生的错误,只需从函数签名即可了解。它还附带一些辅助函数,用于从错误中恢复。

const divide = (a: number, b: number): Effect<number, "DivisionByZeroError"> => {
  if (b === 0) return Effect.fail("DivisionByZeroError");
  return Effect.succeed(a / b);
}
Enter fullscreen mode Exit fullscreen mode

新型或品牌类型

你知道,回顾我以前的职能,我意识到我们可以做得更好。

const divide = (a: number, b: NonZeroNumber) => ...
Enter fullscreen mode Exit fullscreen mode

但是你该如何定义呢NonZeroNumber?如果你直接定义的type NonZeroNumber = number话,就不会阻止人们用“0”来调用它。有一个模式可以解决这个问题:newtypes。没错,Effect 也支持这个:

import { Brand } from "effect"

type NonZeroNumber = number & Brand.Brand<"NonZeroNumber">

const NonZeroNumber = Brand.refined<NonZeroNumber>(
  (n) => n !== 0, // Check if the value is a non-zero number
  (n) => Brand.error(`Expected ${n} to be a non-zero number`)
)
Enter fullscreen mode Exit fullscreen mode

这样,您就知道您的函数不能用任何数字来调用:它需要一种不包括零的特殊类型的数字。

依赖注入

如果你想遵循“控制反转”原则,你可能需要了解“依赖注入”。这个概念其实很简单:一个函数应该能够从它自己的上下文中获取它所需要的内容。

// With a singleton
const read = (filename) => FileReader.read(filename);

// With dependency injection
const read = (reader: FileReader) => (filename) => reader.read(filename);
Enter fullscreen mode Exit fullscreen mode

出于多种原因,这样做会更好,例如解耦事物、便于测试、具有不同的上下文等。

虽然有多个框架可以协助实现这一点,但 Effect 通过使其变得简单而真正地击败了它:将您的依赖项作为 Effect 的第三个参数。

const read = (filename): Effect<File, Error, FileReader> => {
  return Effect.flatMap(FileReader, fileReader => {
    return fileReader.read(filename);
  })
}
Enter fullscreen mode Exit fullscreen mode

结论

您应该考虑 Effect 的原因还有很多。当然,一开始并不容易,您必须学习不同的编程方法。但与许多让您学习“他们”的做事方式的框架不同,Effect 实际上会教给您一些在其他语言中已经得到证明的优秀模式。实际上,Effect 深受 Scala 的 ZIO 的启发,而后者又受到 Haskell 的启发,而 Haskell 至今仍被认为是优秀编程模式的典范之一。

文章来源:https://dev.to/almaju/someone-finally-fixed-javascript-426i
PREV
2021 年完整的全栈路线图(包含资源 + 基于经验)
NEXT
7 个技巧让你无法获得理想的开发者职位