iT邦幫忙

2025 iThome 鐵人賽

DAY 4
0

我們終於要來建立 Effect 了,但… 其實建立 Effect 有非常多的方法,執行 Effect 雖然少一點,但也不只一種,我就介紹幾個比較常用的

使用立即值建立 Effect

就像 Promise.resolvePromise.reject 一樣,你可以把已有的值直接包裝成 Effect

import { Effect } from 'effect'

const successEffect = Effect.succeed(42)
const errorEffect = Effect.fail(new Error('oh no'))

這個雖然好像沒有做什麼,但當你需要將現有的值包成 Effect 時就會有幫助,我自己通常在兩種情況下會用到

  1. 寫測試時
  2. 使用 flatMap 時,這之後會再提到

使用 sync/ try 建立 Effect

我們可以從一個 sync 的 function ,透過 sync 建立 Effect

const answerEffect = Effect.sync(() => 42)

另外可以注意到一個比較特別的事,如果我們像這樣建立 Effect (在 playground 中打開)

const answerEffect = Effect.sync(() => {
  console.log('Effect executed')
  return 42
})

你會注意到這段程式執行時, console.log 沒有顯示,這跟 Promise 並不一樣

const answerPromise = new Promise((resolve) => {
  console.log('promise executed')
  resolve(42)
})

若你執行上面這段 code ,會發現在建立 Promise 時, Promise 就已經開始執行了,這算是 Effect 跟 Promise 一個很大的不同,之後有機會,我們再來說明這樣的特性有什麼好處

如果你有 function 有可能拋出錯誤,而你希望將錯誤捕捉,變成 Effect 的 error 的話,可以使用 try

const maybeAnswerEffect = Effect.try(() => {
  if (Math.random() > 0.5) {
    throw new Error('fail')
  }
  return 42
})

你會發現這樣寫的 maybeAnswerEffect type 是 Effect<number, UnknownException, never> 中間的 UnknownException 代表 Effect 不知道你會拋出什麼錯誤,如果你希望你的錯誤有型態的話,你會需要使用以下版本

const maybeAnswerEffect = Effect.try({
  try: () => {
    if (Math.random() > 0.5) {
      throw new Error('fail')
    }
    return 42
  },
  // 你需要在這邊判斷你的 type 的類型
  catch: (error) => error as Error,
)

畢竟原本 typescript 就沒有針對拋出來的錯誤標註型態的方法,原本的 error 的 type 都是 unknown ,這使得我們需要自己做轉型

從 promise 建立 Effect

我們很常會需要從 promise 建立 Effect ,畢竟大部份的套件在執行非同步的動作時都是回傳 promise 而不會是 Effect ,跟從同步的 function 建立時同樣,我們有 promisetryPromise
兩個版本,可以建立沒有 error ,跟有預期 error 的 Effect

const answerEffect = Effect.promise(() => Promise.resolve(42))
const maybeAnswerEffect = Effect.tryPromise({
  try: () => {
    return new Promise((resolve, reject) => {
      if (Math.random() > 0.5) {
        reject(new Error('fail'))
        return
      }
      resolve(42)
    })
  },
  catch: (error) => error as Error,
})

話說到這邊,你可能會有兩個疑問

  1. 如果我們用 Effect.promise ,結果裡面的 promise reject 了會怎麼樣,答案是視為發生了不可預期的錯誤,導致整個 Effect 會終止
  2. 如果在 catch function 不做型態轉換可以嗎:答案是非常不建議,因為 error 的 type 會變成 unknown
    這都會在第 6 篇 「Effect 中的錯誤」有更詳細的說明

執行 Effect

說了那麼多怎麼建立 Effect ,但上面都沒有一個 Effect 實際跑起來,到底要怎麼執行 Effect ,以及為什麼建立出來的 Effect 都不會直接執行,這邊將一次說明

先來講怎麼執行建立出來的 Effect ,最常用的是 Effect.runPromise 這會將 Effect 轉成 Promise 開始執行

const answerPromise = Effect.runPromise(answerEffect)

另外也有其它的執行方法,比如 Effect.runSync 可以用來同步的執行 Effect ,但要注意的是,一旦給了 Effect.runSync 非同步的 Effect ,比如是使用 Effect.promise 從 promise 進行轉換的,這個 function 就會拋出錯誤

那為什麼 Effect 不會直接執行呢? Effect 其實是像設計藍圖一樣的東西,意思是你可以透過 Effect 先規劃好你的程式,執行時要有幾個 concurrency 同步執行,要怎麼處理錯誤,錯誤要不要重試,等到都設計好後才開始執行你的程式,這些我們會在接下來的幾個章節一個一個來看該怎麼做

介紹到這邊你可能注意到了一件事,不論是 sync 的 Effect ,或是 promise 這種非同步的 Effect ,它們的 type 都是一樣的,例如 Effect.succeed(42) 的 type 是 Effect<number, never, never> ,而 Effect.promise(() => Promise.resolve(42)) 也是 Effect<number, never, never> , Effect 內部會自動偵測這個 Effect 是同步的還是非同步的,有機會再來展示這個特性有什麼好處跟壞處

下一篇要稍微開始展示 Effect 的能力,我們將來中 Effect 中怎麼處理 concurrency

Reference


上一篇
2. 為什麼需要 Effect, Promise 不夠嗎
下一篇
4. Effect 的基本使用
系列文
Effect 魔法:打造堅不可摧的應用程式5
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言