這篇要來介紹的是 @effect/platform , Effect 將不同平台的 API ,主要是 Browser 跟 Node.js 中可以共通的部份抽出來了一層 Effect 的 service ,讓你可以透過這些 service 的介面,使用同樣的 code 在不同的平台上執行,我們一起來看吧
這次的 sample code 在 https://github.com/DanSnow/ithelp-2025-ironman-sample-codes/tree/main/platform-demo
要使用 @efect/platform
除了安裝 @effect/platform
外,通常你還需要安裝你需要執行的 platform 專屬的實作套件,我們這邊就以 Browser 跟 Node.js 兩個環境進行示範,首先,先來裝一些必要的套件
$ pnpm add effect @effect/platform @effect/platform-node @effect/platform-browser
再來安裝一些跟 demo 相關的套件
$ pnpm add --save-dev vite tsx @tsconfig/node22 typescript
這次的目標假設是我們要從 JSON Placeholder 這個提供 mocking API 的服務上面抓下 10 篇 post ,然後儲存起來吧,首先我們先用 platform 裡的 HttpClient 把 post 抓下來
import { Effect, Schema, pipe } from 'effect'
import { HttpClient, HttpClientResponse } from '@effect/platform'
// 定義一下抓下來的資料的 schema
const PostSchema = Schema.Struct({
id: Schema.Number,
title: Schema.String,
body: Schema.String,
})
type Post = typeof PostSchema.Type
// 準備個 parse body 的 function
const parsePostBody = HttpClientResponse.schemaBodyJson(PostSchema)
// 實際抓取 post 的 function
const fetchPost = Effect.fn('fetchPost')(function* (id: number) {
const post = yield* pipe(
HttpClient.get(`https://jsonplaceholder.typicode.com/posts/${id}`),
Effect.flatMap((response) => parsePostBody(response)),
)
return post
})
再來我們使用 KeyValueStore 來儲存
import { KeyValueStore } from '@effect/platform'
// 定義一下我們的 KeyValueStore 裡存的都是 Post
const { tag: PostStore, layer: PostStoreLayer } = KeyValueStore.layerSchema(PostSchema, 'PostStore')
// 儲存 post
const savePost = Effect.fn('savePost')(
function* (id: number, post: Post) {
const { set } = yield* PostStore
yield* set(`post-${id}`, post)
},
// 原始的 KeyValueStore 是只能存 string 的,這個 layer 會負責做驗證,並將資料轉換成 json
Effect.provide(PostStoreLayer)
)
最後組合起來
export function fetchAndSavePosts(count: number) {
return pipe(
Array.makeBy(count, (i) =>
pipe(
// 先抓 post
fetchPost(i + 1),
// 再送去儲存
Effect.flatMap((post) => savePost(i + 1, post)),
),
),
Effect.allWith({ concurrency: 2 }),
)
}
到這邊我們的主程式就完成了,以上內容你可以在 core.ts 找到
再來我們要讓我們的程式可以在 Node.js 上執行,其實並不難,我們只需要把 core 裡用到的 service 都提供一份平台對應的實作的 layer 就行了
// node.ts
import { Effect, Layer, pipe } from 'effect'
import { fetchAndSavePosts } from './core'
import { NodeHttpClient, NodeKeyValueStore } from '@effect/platform-node'
const NodeLive = Layer.mergeAll(
// 指定使用 undici 來取得資料
NodeHttpClient.layerUndici,
// 指定 KeyValueStore 將資料存在 .cache 這個資料夾
NodeKeyValueStore.layerFileSystem('.cache')
)
pipe(
fetchAndSavePosts(10),
// provide 上面那個給 Node.js 的 layer
Effect.provide(NodeLive),
Effect.runPromise
)
到這邊你可以試著執行看看,再去檢查一下 .cache
資料夾裡是不是有剛剛抓下來的資料
$ pnpm exec tsx node.ts
沒意外應該要有 post-1
, post-2
... 的檔案在 .cache
資料夾中
在 Browser 中執行跟 Node.js 要做的事情很像,就是提供一份 Browser 中使用的 layer 就可以了
import { Effect, Layer, pipe } from 'effect'
import { fetchAndSavePosts } from './core'
import { FetchHttpClient } from '@effect/platform'
import { BrowserKeyValueStore } from '@effect/platform-browser'
const BrowserLive = Layer.mergeAll(
// 指定用 fetch 來抓資料
FetchHttpClient.layer,
// 指定用 local storage
BrowserKeyValueStore.layerLocalStorage
)
pipe(
fetchAndSavePosts(10),
// 提供 browser 用的 layer
Effect.provide(BrowserLive),
Effect.runPromise
)
我還在範例 code 中準備了一個 index.html
,我們可以試著執行 vite
來觀察看看結果
$ pnpm exec vite
之後打開 http://localhost:5173 ,並打開 devtool 看一下 Network 跟 local storage,沒意外應該也會看到資料
這篇我們介紹了 @effect/platform
的這個 Effect 官方提供的套件,這個套件可以幫助我們在不同的平台上使用同樣抽像化的 API ,這增加了我們程式的跨平台的能力與更容易測試,下一篇要來介紹的是另一個 Effect 的官方套件: @effect/ai