JavaScript 生態系統正以驚人的速度發展。當您熟悉某種技術時,就會出現大量新方法。其中一些(例如 TypeScript)獲得了廣泛採用,而另一些(例如 CoffeeScript)則悄悄消失。每項創新最初都會引起人們的興奮,但隨著時間的推移,社區經常分裂,批評者最終會產生自己的框架。這種無止盡的循環讓我對聲稱可以解決所有問題的最新「神奇」框架越來越警惕。我已經從尋求工具作為解決方案轉變為擁抱對模式的理解,而不是不斷追求新技術。

這就是為什麼我向您指出針對 TypeScript 專案的特殊工具,不僅僅是另一個工具,而是鼓勵良好實踐的範例: Effect

讓我們來看看為什麼你應該踏出這一步。

彩色函數

您是否曾經問過自己,您的功能是什麼顏色?

讓我為您總結一下。想像一下您的程式碼庫中有藍色和紅色函數。規則很簡單:您可以在藍色函數中使用紅色函數,但反之則不行。那不是一場惡夢嗎?現在用“async”替換藍色。是的,你在 Javascript 得到了函數著色。

那我們該如何對抗這種著色問題呢?如果我們想刪除彩色函數,我們需要建立某種包裝器,僅在需要時使用 Promise。例如「未來」還是…「效果」?

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));

使用EffectPromise主要差異在於如何處理並發。 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
 */

雖然Effect 並沒有消除JavaScript 中固有的非同步/同步差異(函數著色),但透過使用纖程處理非同步操作,它允許同步函數呼叫非同步效果,而不會使其本身成為非同步,從而在很大程度上緩解“著色”問題。

類型安全錯誤

我們來看看這個函數:

const divide = (a: number, b: number) => a / b;

我們這裡剛剛引入了一個問題,我們不能除以零。那麼讓我們稍微重構一下程式碼:

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

你覺得不錯嗎?它不是。因為它不是類型安全的。想要使用您的函數的人不會知道您的函數可能會拋出異常。對於像這樣的簡單函數來說,這可能看起來微不足道,但是當您有數十個潛在錯誤時,它可能會變成一場噩夢。其他較成熟的語言有諸如EitherResult之類的概念來表示類型安全錯誤。看起來像這樣:

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 };

使用 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);
}

新類型或品牌類型

你知道,回顧我以前的職能,我意識到我們可以做得更好。

const divide = (a: number, b: NonZeroNumber) => ...

那麼如何定義NonZeroNumber呢?如果您只是type NonZeroNumber = number它不會阻止人們用「0」來呼叫它。有一個模式:新類型。是的,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`)
)

這樣,您就知道您的函數不能使用任何數字來呼叫:它需要一種不包括零的特殊類型的數字。

依賴注入

如果您想遵循「控制反轉」原則,您可能需要研究「依賴注入」。這個概念非常簡單:函數應該能夠從自己的上下文中存取它所需要的內容。

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

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

出於多種原因,最好這樣做,例如解耦事物、允許輕鬆測試、具有不同的上下文等。

雖然有幾個框架可以幫助實現這一點,但 Effect 確實透過簡化來粉碎它:將依賴項作為 Effect 的第三個參數。

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

結論

您應該考慮 Effect 的原因還有很多。當然,一開始並不容易,您必須學習以不同的方式編碼。但與許多讓你學習「他們的」做事方式的框架相反,Effect 實際上教你好的模式,這些模式已經在其他語言中得到了證明。實際上,Effect 很大程度上受到了 Scala 中的 ZIO 的啟發,而 Scala 本身也受到了 Haskell 的啟發,而 Haskell 至今仍被認為是良好程式模式的頂峰之一。


原文出處:https://dev.to/almaju/someone-finally-fixed-javascript-426i


共有 0 則留言