我們終於要來建立 Effect 了,但… 其實建立 Effect 有非常多的方法,執行 Effect 雖然少一點,但也不只一種,我就介紹幾個比較常用的
就像 Promise.resolve
跟 Promise.reject
一樣,你可以把已有的值直接包裝成 Effect
import { Effect } from 'effect'
const successEffect = Effect.succeed(42)
const errorEffect = Effect.fail(new Error('oh no'))
這個雖然好像沒有做什麼,但當你需要將現有的值包成 Effect 時就會有幫助,我自己通常在兩種情況下會用到
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 ,跟從同步的 function 建立時同樣,我們有 promise
與 tryPromise
兩個版本,可以建立沒有 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,
})
話說到這邊,你可能會有兩個疑問
- 如果我們用
Effect.promise
,結果裡面的 promise reject 了會怎麼樣,答案是視為發生了不可預期的錯誤,導致整個 Effect 會終止- 如果在
catch
function 不做型態轉換可以嗎:答案是非常不建議,因為 error 的 type 會變成 unknown
這都會在第 6 篇 「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