iT邦幫忙

2025 iThome 鐵人賽

DAY 5
0

在前一篇講了那麼多怎麼建立 Effect 跟如何執行,總覺得好像少了什麼東西,條件判斷的 if 還有像迴圈的 for 這些東西跑到哪邊去了,其實很簡單的,這邊想要介紹兩種不同風格的寫法

Functional 風格

在這種風格中,就像之前所介紹的,會將程式拆成更多的小部份,並加以組合,之前也提到過 Effect 本身就是一個 functional 風格的套件,本身其實就已經提供了非常多 functional 風格的寫法

資料轉換 map, flatMap

當你需要將資料做轉換時,你就可以用到 map 或是 flatMap ,例如我們有個包裝了 promise 的 Effect ,而你需要將結果轉換成你需要的格式時,你可以這樣做

const transformedDataEffect = pipe(
  Effect.promise(() => fetchData()),
  Effect.map((data) => transform(data)),
)

如果你的轉換需要使用到另一個 Effect ,例如需要用到回傳 promise 的轉換,或是需要抓另一個資料

const dataEffect = pipe(
  Effect.promise(() => fetchData()),
  Effect.flatMap((data) => fetchAnotherData(data.id)),
)

if / loop

Effect 也有提供 ifloop ,可以像這樣用

const conditionalEffect = pipe(
  Effect.promise(() => fetchData()),
  Effect.flatMap((data) => Effect.if(data < 10, {
    onTrue: () => Effect.succeed(data),
    onFalse: () => Effect.fail(new Error('data is >= 10')),
  }))
)

這就像是

if (data < 10) {
  return Effect.succeed(data)
} else {
  return Effect.fail(new Error('data is >= 10'))
}

loop 則是像這樣,例如計算從 1 到 10 ,每個數字都做平方

const effect = Effect.loop(1, {
  // 判斷條件是否要繼續執行
  while: (state) => state <= 10,
  step: (state) => state + 1,
  // 實際計算的部份
  body: (state) => Effect.succeed(state * state),
})

console.log(Effect.runSync(effect)) // [1, 4, 9, ..., 100]

不過其實像這樣刻意用 functional 的方法做判斷其實有點多此一舉的感覺,因此通常我不會用到這樣的寫法,而是接下來要介紹的我比較常用的方法,若對這種寫法有興趣的可以閱讀相關的文件,文件中還有更多不同的 function

gen 使用 generator 寫 Effect

這邊要介紹的是一個 js 中比較不常用的語法, generator

// 請注意這邊的 `*`
function * generator(n: number) {
  for (let i = 0; i < n; i++) {
    yield i
  }
}

console.log(Array.from(generator(10))) // [0, 1, 2, ..., 9]

這個語法有個特色是,當執行到 yield 時,會暫停 function 的執行,並回傳 yield 指定的值,讓上層呼叫這個 generator 的決定要不要續繼執行

透過這個特性 Effect.gen 使用 generator ,讓你可以用比較接近一般的程序式的寫法也可以寫 Effect 的程式

const effect = Effect.gen(function * () {
  // 注意這邊用的是 `yield*` 而不是 `yield`
  const data = yield* Effect.promise(() => fetchData())
  if (data < 10) {
    return data
  }
  yield* Effect.fail(new Error('data >= 10'))
  // 這邊因為上面的 `Effect.fail` 其實就不會續續執行下去了
})

你可以透過 yield* 將任何的 Effect 傳回去給 Effect 的 runtime 執行,並獲得執行的結果

FP vs Effect.gen 該如何選擇

我自己平常的習慣是,若有以下情況就選擇用 Effect.gen ,若沒有則用 FP 的寫法

  1. 有需要使用到 if, while (通常而言你不該用到 for 這種固定執行次數的,這在後面的 concurrency 時會講到)
  2. 不需要做重覆動作,或是有特別的 retry 等動作

當然,其實兩種寫法混用也完全沒問題,例如以下範例

const effect = pipe(
  Effect.gen(function * () {
    const data = yield* pipe(
      fetchData(),
      Effect.map((data) => transform(data))
    )
    return data
  }),
  Effect.map((data) => anotherTranform(data))
)

這篇介紹了基本的邏輯判斷,與 Effect.gen 這個工具可以讓你用類似一般的程序式的寫法撰寫 Effect 的程式,在下一篇我們終於要稍微看到 Effect 可以怎麼幫助我們更好的控制流程了

Reference


上一篇
3. 第一個 Effect
系列文
Effect 魔法:打造堅不可摧的應用程式5
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言