Johnny Wang BlogJohnny Wang Blog
Articles
Project
Live2d
johnnywang/book
Articles
Project
Live2d
johnnywang/book
  • About Me

    • 關於 Johnny
    • 文章清單
  • Javascript

    • View Transitions - 認識並使用原生 document 轉場效果
    • 用 Web Container 打造自己的線上 NodeJS 開發環境
    • 來試用看看原生 Web Popover API
    • 在電腦裡搞一個 RWKV AI 小助手
    • 遊戲 App 素材解包學習紀錄
    • 動手自己做一個 ChatGPT UI 工具吧
    • Create a React Server Components Project without NextJS - 製作一個不依賴 NextJS 的 React Server Components 專案
    • 如何在 Vuepress 裡快樂寫 React
    • 從 Mock Service Worker 源碼中學習
    • 擺脫 Node modules 地獄,擁抱 Yarn Plug'n'Play(PnP)
    • 快速上手 NextJS v13 - 基礎觀念 AppRouter 篇
    • 快速上手 NextJS v13 - Data Fetching, Caching, Revalidating 篇
    • 用 Socket.io 搭配 Matterjs 製作一款 Real-Time Canvas 聊天室(文長慎入)
    • 如何只用一支 CDN 及 4行設定,讓瀏覽器讀懂 Typescript, React, Vue
    • 用 tsup 快速建立 Typescript 開發環境
    • 開發 Email EDM 你可以更輕鬆
    • 關於我的 Side project - Maju Web Editor
    • Web URL 中神奇的 createObjectURL method
    • Awesome Vite 一個由社群維護的 template 集大成
    • Vue、React 使用心得分享(文長慎入)
    • 一起動手用 Socket.io 和 Peerjs 打造 WebRTC 即時視訊
    • Temporal Typescript SDK 學習筆記
      • Temporal 是什麼?
      • Task
      • Activity
      • Workflow
      • Worker
      • Client
      • Signals & Querys
      • Continue as New
      • Install Example
      • 其他範例
      • Reference
    • React Web3 Storage
    • React useState 取得最新值
    • Babel 7 Decorator 的神奇小問題
    • 用 Koa + Vite + Pinia 打造基礎 SSR 環境
    • Live2d 官方範例改寫
    • 如何把 Video 畫在 Canvas 上
    • 史上最簡單的 Webpack 5 教學
    • 在瀏覽器中直接 import Vue SFC 開發起來
    • 手寫一個可中斷的 delay promise
    • NodeJS 輕量開發框架 Expressjs 與 Koa2 的區別
    • 用 2D 物理引擎 Matterjs 製作經典馬力歐 1-1
    • 原生 Javascript 的類型標註工具 JSDoc
    • Pinia - Vuex 的後繼者
    • 用 Nodejs 寫個 FTP command line 工具
    • Vue3 Server Render 手把手帶你搭建
    • 你真的懂 Event Loop 嗎
    • Babel7 基本介紹與使用
    • Vue/Vitejs 部分源碼解析
    • Vuejs 依賴追蹤 2020 版
    • Vuejs 依賴追蹤 2019 版
    • Js literal 模板編譯
    • 用 JavaScript 鎖定用戶調整畫面比例
    • Date.now() 與 new Date()
    • Promise 相關知識
    • Web Component 學習筆記
    • TypeScript 基礎篇
    • TypeScript 進階篇
    • MVC, MVVM, MVI 軟體設計架構
    • MVVM 簡單模擬框架實作
  • CSS & Sass

    • 2024 CSS 年度報告筆記
    • 2021 CSS 年度報告筆記
    • 如何不用 setTimeout 幫 display: none 的 DOM 加動畫
    • 純 CSS 實作星球環繞動畫效果
    • 差點錯過的 Tailwindcss 入門學習筆記
    • Tailwindcss 進階學習筆記
    • Sass, SCSS Built-In-Modules 內建模組與函數
    • SCSS Parent Selector
    • CSS 畫面鎖橫屏時,滾動的問題!!
    • Safari 使用 animation 時動態產生 rem 的坑
    • CSS: mix-blend-mode 屬性混合圖層動畫
  • Memo

    • Javascript

      • Yarn Plug'n'Play (PnP) 的 VSCode 設定方式
      • 如何用 Web API 讀取剪貼簿內容
      • CSP (Content Security Policy) 是什麼?
      • Astro 入門學習筆記
      • 如何解決 Astro 套用主題切換時,畫面抖動瞬閃問題
      • Rspack - 以 Rust 打造的快速構建工具
      • Typescript v5 Decorator 學習筆記
      • Sequelize 筆記
      • ES2022 學習筆記
      • Crypto 密碼加密方法
      • 實作 Test 的注意事項
      • Jest 測試工具 - 基礎篇
      • 正則表達式
      • Rxjs 學習筆記
      • Gulp 學習筆記
      • Clean Code Javascript 學習
      • JS 實戰開發特殊小技巧
    • React

      • React forwardRef 使用筆記
      • 如何在 Nextjs 中使用 middleware set cookie
      • React Styled-Components 基礎篇
      • React Styled-Components 進階篇
      • React Unit Test
      • React Testing Library
      • React Emotion Basic
      • React Styled-JSX
      • React 18 - Concurrent Features Memo
      • useEffect 的高階封裝範例
      • React useContext & useReducer 搭配
      • React Router Config 筆記
    • Vue

      • Cypress Vue
      • Vue 單元測試學習筆記
      • 學習 Vuetify 的一些筆記
      • Vuex 學習筆記
    • React Native

      • React Native - basic
      • Expo Basic
      • Expo Router
      • Expo Build
      • Issues
    • GraphQL

      • GraphQL 學習筆記 - 基礎篇
      • GraphQL 學習筆記 - 進階篇
      • Apollo Client 學習使用筆記
      • GraphQL Memo
    • Parse

      • Parse Javascript 文檔閱讀筆記
      • Parse User Object 章節
      • Parse Session Object 章節
      • Parse Schema Object 章節
      • Parse Cloud Code 章節
    • CSS

      • Sass, SCSS 基礎筆記
    • Docker

      • Docker 基礎技術
      • Dockerfile 技術篇
      • Docker-compose 技術篇 - 3.7
      • Kubernetes 學習筆記
      • ArgoCD 學習筆記
      • Podman 學習筆記
      • Colima 安裝使用筆記
    • Git Learning

      • Git 版本控制
      • Git 基礎使用 - init, clone, add, commit, status, log
      • Git 分支
      • Git Merge 分支合併指令
      • Git Stash 保存紀錄
      • Git 基礎復原指令 - restore
      • Remote 遠端協同工作 - remote, fetch, pull, push
      • Git Tag 標籤 - tag
      • Git 設定 - config
      • Git 檔案比對與差異 - diff
      • Git Rebase 定義分支的參考基準
      • Subtree 子樹
      • Git Reflog 指令紀錄
      • Git Filter Branch
      • Git Crypt 使用筆記
      • Git 客製化工具
    • Bash

      • Bash 基礎概念
      • Variable 變數
      • Script 腳本撰寫
      • Condition 流程控制
      • Operation 算數表達式
      • Function 函數
      • Command Line Params 指令列參數處理
    • Python

      • Pyenv with virtualenv 配置
    • Patterns

      • Introduce
      • Design Patterns

        • Singleton Pattern
        • Compound Pattern
        • Proxy Pattern
        • Hooks Pattern
        • Observer Pattern
        • Mediator/Middleware Pattern
        • HOC Pattern
        • Factory Pattern
        • Render Props Pattern
        • Flyweight Pattern
        • Container/Presentational Component
        • Prototype Pattern
        • Mixin Pattern
        • Provider Component
        • Command Component
        • Module Pattern
      • Render Patterns

        • Rendering Patterns 渲染模式介紹
    • FE 性能優化

      • 前端性能優化
      • 靜態資源優化
      • 頁面渲染架構優化
      • Server 與 Network 優化
      • 開發流程、監控體系優化
    • Ollama 筆記
    • Nginx JS module 基本使用筆記
    • FlowiseAI 使用介紹筆記
    • Github Copilot 使用筆記
    • Traefik Memo
    • API First 學習筆記
    • What is AC - Acceptance Criteria 驗收條件
    • Mermaid 學習筆記
    • 串接 Sonarcube 筆記
    • Youtube Data API 基礎使用筆記
    • FB API 學習筆記
    • VSCode 好用快捷鍵
    • 在 Simulator 上開發測試
    • 問題修正紀錄
  • References

    • 全端開發學習資源
    • 實用工具
    • 網頁前端課程目錄
  • Daily

    • 中醫學習

      • 中醫自學方向
      • 黃帝內經

        • 黃帝內經-素問
      • 基礎理論

        • 中藥治病
        • 中藥產地與採收
        • 陰陽五行
        • 精氣血津液
        • 整體觀與臟象
        • 病因病機
        • 辨證論治
        • 四診八綱
        • 五運六氣
        • 經絡
      • 診斷學

        • 辯證
        • 望診
        • 聞診
        • 問診
        • 脈診簡史
        • 切診
        • 常見脈象與臨床意義
        • 經脈辨證
        • 中醫的六邪在疾病初期、後期分別有那些症狀
      • 中藥學

        • 中藥基礎理論
        • 常見中藥材
        • 解表藥(30)
        • 清熱藥(82)
        • 瀉下藥(16)
        • 利水渗湿药(8)
        • 化痰止咳平喘药(15)
        • 止血藥(8)
        • 開竅-平肝-安神藥(11)
        • 化濕藥(5)
        • 祛風濕藥(12)
        • 理氣藥(13)
        • 活血祛瘀藥(7)
        • 消食藥(4)
        • 驅蟲藥(6)
        • 祛寒药(6)
        • 補虛藥(43)
        • 收斂藥(13)
      • 方劑學

        • 總論
        • 解表劑
        • 瀉下劑
        • 清熱劑
        • 理血劑
        • 去暑劑
        • 去濕劑
      • 針灸學

        • 特定穴
      • 其他

        • 內證觀察筆記 上篇【內證漫談】
        • 內證觀察筆記 中篇【太極器官、五藏】
        • 內證觀察筆記 下篇【十二正經】
        • 磁力對生理的影響
    • 易經學習

      • 易經入門
    • 道德經學習

      • 道德經學習重點筆記
      • 第一章 道可道,非常道
      • 第二章 夫唯弗居,是以不去
      • 第三章 不尚賢,使民不爭
      • 第四章 道沖而用之或不盈,淵兮似萬物之宗
      • 第五章 天地不仁,以萬物為芻狗,多聞數窮,不如守中
      • 第六章 谷神不死,是謂玄牝
      • 第七章 天長地久。天地所以能長且久者,以其不自生,故能長生。
      • 第八章 上善若水。水善利萬物而不爭,處眾人之所惡,故幾於道
      • 第九章 功成身退,天之道也。
      • 第十章 載營魄抱一,能無離乎﹖
      • 第十一章 有之以為利,無之以為用
      • 第十二章 五色令人目盲,五音令人耳聾,五味令人口爽,馳騁畋獵令人心發狂,難得之貨令人行妨。
      • 第十三章 寵辱若驚,貴大患若身。愛以身為天下,若可托天下
      • 第十四章 是謂無狀之狀,無物之象,是謂惚恍。迎之不見其首,隨之不見其後。執古之道,以御今之有。
      • 第十五章 保此道者,不欲盈。夫唯不盈,故能蔽而新成。
      • 第十六章 致虛極,守靜篤。萬物並作,吾以觀復。
      • 第十七章 太上,下知有之。悠兮其貴言,功成事遂,百姓皆謂我自然。
      • 第十八章 大道廢,有仁義;智慧出,有大偽;
      • 第十九章 絕聖棄智,民利百倍。見素抱樸,少私寡欲
      • 第二十章 絕學無憂。眾人皆有餘,而我獨若遺。
      • 第二十一章 道之為物,惟恍惟惚。惚兮恍兮,其中有象;恍兮惚兮,其中有物。
      • 第二十二章 曲則全,枉則直,窪則盈,敝則新,少則得,多則惑。是以聖人抱一為天下式
      • 第二十三章 希言自然。天地尚不能久,而況於人乎?信不足焉,有不信焉。
      • 第二十四章 企者不立,跨者不行
      • 第二十五章 有物混成,先天地生。人法地,地法天,天法道,道法自然。
      • 第二十六章 重為輕根,靜為躁君。輕則失本,躁則失君。
      • 第二十七章 善行無轍跡,善言無瑕謫。故善人者,不善人之師;不善人者,善人之資。不貴其師,不愛其資,雖智大迷,是謂要妙。
      • 第二十八章 知雄守雌,為天下谿,知榮守辱,為天下谷
      • 第二十九章 天下神器,不可為也,是以聖人去甚,去奢,去泰。
      • 第三十章 以道佐人主者,不以兵強天下
      • 第三十一章 夫兵者,不祥之器,物或惡之,故有道者不處。
      • 第三十二章 道常無名,樸雖小,天下莫能臣也。
      • 第三十三章 知人者智,自知者明。
      • 第三十四章 大道氾兮,其可左右。以其終不自為大,故能成其大。
      • 第三十五章 執大象,天下往。往而不害,安平太。樂與餌,過客止。
      • 第三十六章 將欲歙之,必固張之;將欲弱之,必固強之
      • 第三十七章 道常無為而無不為
      • 第三十八章 上德不德,是以有德;下德不失德,是以無德
      • 第三十九章 昔之得一者,天得一以清。不欲琭琭如玉,珞珞如石。
      • 第四十章 反者道之動,弱者道之用。天下萬物生於有,有生於無
      • 第四十一章 上士聞道,勤而行之;大方無隅,大器晚成,大音希聲,大象無形,道隱無名。
      • 第四十二章 道生一,一生二,二生三,三生萬物。
      • 第四十三章 天下之至柔,馳騁天下之至堅
      • 第四十四章 名與身孰親﹖身與貨孰多﹖得與亡孰病﹖
      • 第四十五章 大成若缺,其用不弊。大盈若沖,其用不窮。
      • 第四十六章 天下有道,卻走馬以糞。天下無道,戎馬生於郊。
      • 第四十七章 不出戶,知天下;不窺牖,見天道。
      • 第四十八章 為學日益,為道日損。損之又損,以至於無為。
      • 第四十九章 聖人無常心,以百姓心為心。
      • 第五十章 出生入死。生之徒,十有三;死之徒,十有三;人之生,動之死地,亦十有三。
      • 第五十一章 道生之,德畜之,物形之,勢成之。
      • 第五十二章 天下有始,以為天下母。以知其子,既知其子,復守其母,沒身不殆。
      • 第五十三章 使我介然有知,行於大道,唯施是畏。大道甚夷,而人好徑。
      • 第五十四章 善建者不拔,善抱者不脫,子孫以祭祀不輟。
      • 第五十五章 含德之厚,比於赤子。知和曰常,知常曰明。益生曰祥。心使氣曰強。
      • 第五十六章 知者不言,言者不知。
      • 第五十七章 以正治國,以奇用兵,以無事取天下。
      • 第五十八章 其政悶悶,其民淳淳;其政察察,其民缺缺。禍兮福之所倚,福兮禍之所伏。
      • 第五十九章 治人事天,莫若嗇。夫唯嗇,是謂早服
      • 第六十章 治大國,若烹小鮮。夫兩不相傷,故德交歸焉。
      • 第六十一章 大國者下流,天下之交。天下之牝,牝常以靜勝牡,以靜為下。
      • 第六十二章 道者萬物之奧。雖有拱璧以先駟馬,不如坐進此道。
      • 第六十三章 為無為,事無事,味無味。圖難於其易,為大於其細;
      • 第六十四章 合抱之木,生於毫末;九層之臺,起於累土;千里之行,始於足下。為者敗之,執者失之。
      • 第六十五章 古之善為道者,非以明民,將以愚之。
      • 第六十六章 是以欲上民,必以言下之。欲先民,必以身後之。
      • 第六十七章 天下皆謂我道大,似不肖。夫唯大,故似不肖。
      • 第六十八章 善為士者不武,善戰者不怒,善勝敵者不與
      • 第六十九章 吾不敢為主而為客,不敢進寸而退尺。禍莫大於輕敵,輕敵幾喪吾寶。
      • 第七十章 吾言甚易知,甚易行。天下莫能知,莫能行。
      • 第七十一章 知不知上,不知知病。夫唯病病,是以不病。
      • 第七十二章 民不畏威,則大威至。無狎其所居,無厭其所生。夫唯不厭,是以不厭。
      • 第七十三章 天之道,不爭而善勝,不言而善應,不召而自來,繟然而善謀。
      • 第七十四章 民不畏死,奈何以死懼之?若使民常畏死,而為奇者,吾得執而殺之,孰敢?
      • 第七十五章 民之饑,以其上食稅之多,是以饑。
      • 第七十六章 人之生也柔弱,其死也堅強。萬物草木之生也柔脆,其死也枯槁。
      • 第七十七章 天之道,其猶張弓與﹖高者抑之,下者舉之;有餘者損之,不足者補之。
      • 第七十八章 天下莫柔弱於水,而攻堅強者莫之能勝,以其無以易之。
      • 第七十九章 和大怨,必有餘怨,安可以為善﹖
      • 第八十章 小國寡民。使有什伯之器而不用,使民重死而不遠徙。
      • 第八十一章 信言不美,美言不信。天之道,利而不害;聖人之道,為而不爭。
    • 人類真相推廣協會

      • 人類真相介紹
      • 人生大挑戰-閱讀筆記
      • 書籍-人生大挑戰

        • 序文
        • 童年的回憶
        • 賺外快的童年
        • 養家的童年
        • 少年時期的回憶
        • 粉紅睡衣女鬼的祕密
        • 麵線、甘蔗和賽鴿
        • 我在黑社會的日子
        • 『台北一條龍』
        • 賭徒‧妻子‧盤仔人
        • 人鬼之戰—正邪不分的恐怖
        • 開展創業石銅雕畫的日子
        • 渡畜牲者‧瞎掰鬼與邪靈
        • 無所不在的陷阱—邪靈的詭計
        • 鬼屋‧符令‧大揭祕
        • 妖魔鬼怪大變身—邪靈與動物
        • 乩童與宮廟的祕密
        • 活鬼纏身的恐怖亂象
        • 前面親兄弟、後面無情義的假面
        • 前面手牽手、後面下毒手的險境
        • 人心險惡、五馬分屍所逼自殺的真相
        • 自殺後的奇遇—人死後的世界
        • 我自殺後的奇遇—「陰間地府處」與「陰府大本營」
        • 我被趕出家門、面臨眾叛親離的苦境
        • 其他
    • 英文學習

      • 自我介紹
      • 簡單寒暄
      • 指路
      • 買賣
      • 轉接電話
      • 通勤
      • 訂位
      • 數字
    • 金剛經 時間不存在?自我是虛擬?
    • 20250319 外婆過世
    • 說服的藝術:避免陷阱,建立共識
    • 禪修一定要去除「如何」嗎?
    • 不對稱的回報
    • 避免慢性壓力
    • 關鍵溝通表達力
    • 快樂的秘訣
    • 生活在現代社會,維持高品質思考的重要性
    • 心理學家的面相術:解讀情緒的密碼
    • 什麼是真相,真相是什麼
    • 身為一位歷史觀察者(文長慎入)
    • 2022 Leisure Learn
    • 兩年八個月,我從 Garena 畢業了
    • DeFi
    • 我的終生大事,交往到結婚
    • 轉職前端工程師 3 年多的回顧
    • MacOS + Iterm2 + Oh My Zsh
    • 我喜歡的名言佳句
    • Web Interview Preparation

Temporal Typescript SDK 學習筆記

Share On:

FacebookLINEMessengerTelegram

Hi 大家好,這篇是紀錄學習基礎 Temporal Typescript SDK 的介紹與筆記(Temporal 支援多種語言使用),旨在快速學習與上手理解,雖然整理完文章還是很長 XDDDDD(學到崩潰

詳細介紹建議查看官方文件會更清楚喔,本篇是個人閱讀與前輩指點整理而成,感謝前輩指點迷津帶我飛~

Temporal 是什麼?

Temporal 是一個分布式、易擴展、持久且高度可用的工作編排引擎(workflow engine),用統一的 API 替許多日常的定時工作、排期工作進行編排自動化的長時間運行業務邏輯(比如訂閱扣款、爬蟲資料刷新等等)。

其中以五個概念為核心: Task, Activity, Workflow, Worker, Client

Task

temporal 中的 taskQuene 中包含兩個 quene,workflowTaskQuene, activityTaskQuene,兩者分別存放對應的 task,所以 task 也分為 workflowTask, activityTask,兩者的區別後續會提到,這邊可以先大概有個概念,詳細可參考官方的圖如下

  • Temporal Task

Activity

一個包含程式執行環境的活動功能,通常為實際執行程式邏輯,處理的動作比起 workflow 較為單一,activity 在實際被 workflow 使用時會透過 proxyActivities 包裹,才會真正被定義 taskQuene

export async function greet(name: string): Promise<string> {
  return `Hello, ${name}!`;
}

Workflow

一個 Workflow 是由開發者定義的 Temporal 內部最小執行單元,包含一段無副作用的執行過程,每個 workflow execution 都擁有一個本地狀態,並獨佔訪問權,其他 workflow 無法直接訪問,workflow 彼此間以 並行 的方式執行,互不影響,若 workflow 彼此需要溝通可以透過 傳遞 signal 的方式進行

workflow 是一個可重入的過程,包含可恢復、反應式,不論設定多久,或是系統故障,將會自動重啟並重試

  • 可恢復:指 process 在因執行失敗,或者因執行等待而暫停後,可以繼續執行的能力
  • 反應式:指 process 可以對外部事件作出反應的能力

在 workflow 中取得 workflowInfo

呼叫 workflowInfo 可以取得當前 workflow 的資訊,比如 workflowId

import { workflowInfo } from '@temporalio/workflow'

export async function example(name: string): Promise<string> {
  const { workflowId } = workflowInfo()
  return await greet(name);
}

在 workflow 中呼叫 activity

在 workflow 中的環境不是程式環境(此為nodejs),而是一個特殊執行環境,故 activity 方法需透過 proxyActivities 解構之後才能使用,名稱與原來的方法相同

// workflows.ts
// 此為 temporal workflow 執行環境
// 執行流程看似一般程式,但其實背後 temporal 會將不同 task 分發出去給 workers 執行
// 這裏僅算是一個 workflow 工作流程的定義區
import { proxyActivities } from '@temporalio/workflow';
import type * as activities from './activities';

const { greet } = proxyActivities<typeof activities>({
  startToCloseTimeout: '30s',
  // 這邊可以定義被 proxy 的 activity 實際會觸發什麼 taskQuene
  // 讓對應監聽的 worker 去處理這個 activiyTask
  // 預設情況下會觸發 `default` worker
  // taskQuene: 'default'
});

/** A workflow that simply calls an activity */
export async function example(name: string): Promise<string> {
  return await greet(name);
}

在 workflow 中呼叫 child workflow

如果需要在當前 workflow 中調用另一個子 workflow,可以使用兩個方法

  • executeChild: 可回傳 promise await 等子 workflowTask 執行完(推薦)
  • startChild: 丟一個 child workflowTask 執行出去後就不管他
// workflows.ts
import { executeChild, proxyActivities } from '@temporalio/workflow';
import type * as activities from './activities';

const { greet } = proxyActivities<typeof activities>({
  startToCloseTimeout: '30s'
});

export async function childExample({ msg }) {
  return `Johnny is ${msg}`
}

export async function example(): Promise<string> {
  const name = await executeChild(childExample, {
    args: [{ msg: 'very well' }],
    workflowId: `child-example-${Date.now()}`,
    // taskQueue: 'default', // 這裡也可以指定 taskQuene
  })
  return await greet(name);
}

Worker

worker 是 temporal 裡面實際執行 task 的 工作者,每個 worker 會指定監聽的 taskQuene 目標,每當監聽的 quene 新增 task 時就會主動去 poll task 來執行

建立 Worker

建立時綁定每個 worker 對應可以使用的 activities, workflowsPath,但 worker 不一定要綁定 workflow,可以只綁定 activities,並在其他 workflow 中呼叫時透過 taskQuene 指定讓某個特定處理 activities 的 worker 處理

import { Worker } from '@temporalio/worker';
import * as activities from './activities';

const workers = []

async function run() {
  const worker = await Worker.create({
    workflowsPath: require.resolve('./workflows'),
    activities,
    taskQueue: 'default',
  });
  workers.push(worker)

  const activitiesWorker = await Worker.create({
    activities,
    taskQuene: 'hello-world' // proxyActivities 時指定為 hello-world,這個 worker 就會去處理~
  });
  workers.push(worker)

  await Promise.all(workers)
}

run().catch((err) => {
  console.error(err);
  workers.forEach((w) => w.shutdown());
  process.exit(1);
});

Client

client 是提供用戶端開發時進行調用 workflow task 的主要途徑,也可以透過 command line 進行調用

前提是你的 temporal server 已經啟動就緒

Programming

建立新的 temporal workflow client 實例

// client.ts
import { Workflow } from '@temporalio/workflow';
import { Connection, WorkflowClient } from '@temporalio/client';

let client: WorkflowClient;

export async function getClient() {
  if (!client) {
    const connection = await Connection.connect({
      // Connect to localhost with default ConnectionOptions.
      // In production, pass options to the Connection constructor to configure TLS and other settings:
      // address: 'foo.bar.tmprl.cloud', // as provisioned
      // tls: {} // as provisioned
    });

    client = new WorkflowClient({
      connection,
      namespace: 'default', // change if you have a different namespace
    });
  }

  return client;
}

透過 client 物件調用執行 workflow

import { getClient } from './client'
import { example } from './workflows'

async function run() {
  const client = await getClient();
  const handle = await client.start(example, {
    args: ['Temporal'], // 參數傳遞給 example workflow
    taskQueue: 'hello-world', // 加入的 taskQuene 名稱,對應 worker 必須能處理此 workflow
    workflowId: 'example-workflow-id',
    // workflow id reuse policy 參考結尾列表
    workflowIdReusePolicy: WorkflowIdReusePolicy.WORKFLOW_ID_REUSE_POLICY_REJECT_DUPLICATE,
    // retry policy
    retry: {
      maximumAttempts: 20
    }
  })
  const res = await handle.result()
}

run()

Command line

  • How to use tctl?
    可以透過 command line 進行調用 temporal workflow
# 環境變數設為預設(local)
export TEMPORAL_CLI_ADDRESS=
# 執行 workflow
tctl wf start --tq default -i '[workflow-args]' -w [workflow-id] --wt [workflow-type]
# 取消 workflow
tctl wf cancel -w [workflow-id]

Signals & Querys

這兩者都是用在與 workflow 進行溝通傳遞訊息用,但有些微區別

  • Signals
    setHandle 僅可以在 workflow 執行特定動作,無法返回狀態
  • Querys
    setHandle 可以返回 workflow 內部狀態

Signals 範例

以下範例為一個訂閱機制 workflow

// workflows.ts
import { defineSignal, setHandler, sleep } from '@temporalio/worker'

export const cancelSubscription = defineSignal('cancelSignal'); // new

export async function SubscriptionWorkflow(
  email: string,
  trialPeriod: string | number
) {
  let isCanceled = false; // internal variable to track cancel state

  setHandler(cancelSubscription, () => void (isCanceled = true)); // new

  await acts.sendWelcomeEmail(email);
  await sleep(trialPeriod); // sleep will wait a fixed time
  if (isCanceled) {
    await acts.sendCancellationEmailDuringTrialPeriod(email); // new
  } else {
    await acts.sendSubscriptionOverEmail(email);
  }
}

- Invoke by Client

// other-client.ts
import { cancelSubscription } from './workflows';
import client from './client';

function signalWorkflow(workflowId, signalName, ...args) {
  const handle = client.getHandle(workflowId);
  await handle.signal(signalName, ...args);
}

signalWorkflow('my-workflow-id', cancelSubscription)

- Using condition with timeouts

sleep(ms) 將執行延遲固定時間,condition 將執行無限期延遲,直到給定的條件函數判斷返回 true,假設有個檢查函數,我們要判斷檢查是否完全執行完畢可以寫成這樣

// workflows.ts
export const childCompletedSignal = defineSignal(childCompletedSignal)

export async function checkDataWorkflow() {
  let completedChildWorkflows = 0;
  let totalChildWorkflows = 0;

  setHandler(childCompletedSignal, () => {
    completedChildWorkflows += 1;
  });

  const { workflowId } = workflowInfo();
  const dataList = await getChildData(); // activity to call rest api to get childData

  const childHandles = dataList.map((data) => {
    return startChild(checkPartialData, {
      args: [{
        parentWorkflowId: workflowId,
        data
      }],
      workflowId: `check-child-${Date.now()}`
    })
  })

  totalChildWorkflows = childHandles.length;

  // 這邊其實可以用 executeChild 搭配 Promise.all 更直覺,但這只是一個示範
  // wait until all child complete
  await condition(() => completedChildWorkflows === totalChildWorkflows)

  return true
}

export async function checkPartialData({
  parentWorkflowId,
  data
}) {
  // check data...
  // send signal to parent by parentWorkflowId
  await signalWorkflow(parentWorkflowId, childCompletedSignal)
}

甚至還有 inlineSignal,詳情可見Signals官網說明

Querys 範例

透過 query 取得更新資料 workflow 的更新狀態

export const userInfoStatusQuery = defineQuery('userInfoStatusQuery');

export async function updateUserInfoWorkflow() {
  let completedAt = null

  // return workflow status when being queried
  setHandle(userInfoStatusQuery, () => ({
    completed: !!completedAt,
    completedAt,
  }))

  await updateUserInfo() // activity to update user info

  completedAt = new Date();

  return true; // 即使 workflow completed 後,query 此 workflowId 仍然能取得他的狀態
}

Query by Client

跟 Signals 的處理幾乎相同,差別在可以返回狀態

// other-client.ts
import { userInfoStatusQuery } from './workflows';
import client from './client';

async function queryWorkflow(workflowId, signalName, ...args) {
  const handle = client.getHandle(workflowId);
  return await handle.query(signalName, ...args);
}

queryWorkflow('my-workflow-id', userInfoStatusQuery).then(
  (status) => console.log(status.completedAt)
)

custom useState function

  • Link
import * as wf from '@temporalio/workflow';

function useState<T = any>(name: string, initialValue: T) {
  const signal = wf.defineSignal<[T]>(name);
  const query = wf.defineQuery<T>(name);
  let state: T = initialValue;
  return {
    signal,
    query,
    get value() {
      // need to use closure because function doesn't rerun unlike React Hooks
      return state;
    },
    set value(newVal: T) {
      state = newVal;
    },
  };
}

// usage in Workflow file
const store = useState('your-store', 10);
function YourWorkflow() {
  wf.setHandler(store.signal, (newValue: T) => {
    // console.log('updating', newValue) // optional but useful for debugging
    store.value = newValue;
  });
  wf.setHandler(store.query, () => store.value);
  while (true) {
    console.log('sleeping for ', store.value);
    wf.sleep(store.value++ * 100); // you can mutate the value as well
  }
}

// usage in Client file
await handle.signal(store.signal, 30);
const storeState = handle.query<number>(store.query); // 30

Continue as New

  • continue-as-new
  • large-event-histories
    打開 Temporal Web UI 可以看到每個 worker 執行 task 時,左側的編號就是 event 編號,預設最大單一 worker 能夠執行的 events 數量為 50000,超過就必須 renew,建議可以如下面方式以分頁的形式 1000 筆執行一個 partialWorkflow,並透過 while loop 判定是否還有剩餘需執行的內容,避免效能崩潰
// workflows.ts
export async function fetchAllData({
  startPage,
  pageSize
}) {
  let hasNextPage = true;
  let page = startPage;

  while (hasNextPage) {
    hasNextPage = await executeChild(fetchPartialData, {
      args: [page, pageSize],
      workflowId: `fetch-partial-${page}`,
    });

    page += 1;
  }

  return true
}

export async function fetchPartialData(
  page = 0,
  pageSize = 1000
) {
  // getUsers could be your activity to call rest api to get users
  const { userIds, pageInfo } = await getUsers(page, pageSize);

  await Promise.all(
    userIds.map(async (userId) => {
      await executeChild(fetchData, {
        args: [userId],
        workflowId: `fetch-user-${userId}`,
      });
    })
  )

  return pageInfo.hasNextPage
}

export async function fetchData(userId) {
  // do something with userId
}

Install Example

Temporal 官方提供了快速搭建整套環境的 example,只需要使用 npx 快速就可以快速下載試用

下載 example

$ npx @temporalio/create@latest ./myfolder

下載 docker-compose 環境配置

獲取最新的 temporal docker-compose 配置檔案,包含不同環境的設定,下載後啟動即可

$ git clone https://github.com/temporalio/docker-compose.git temporal-server
$ cd temporal-server
$ docker-compose up

Temporal Web UI

Temporal 現在提供兩個 version 的 Web UI,就依照喜好選擇你要看哪個摟~,預設配置裡兩個會同時啟動(未來不知道會不會把 v1 移除)

  • Temporal Web UI v1: port 8088
  • Temporal Web UI v2: port 8080

其他範例

製作 timeout 效果

const wrapTimeout = (targetHandle, maxWaitSeconds = 30) => {
  return Promise.race([
    targetHandle,
    new Promise((r) => setTimeout(r, maxWaitSeconds * 1000)),
  ]);
}

const runWorkflow = async () => {
  const wfHandle = await client.start(myWorkflow, {
    args: [],
    workflowId: 'my-workflow-id'
  });
  // workflow 跑超過 30s 就直接往後不等,避免卡住
  await wrapTimeout(wfHandle.result(), 30)

  const status = await wfHandle.query(checkStatusQuery);
  return status;
};

獲取 workflow 狀態

Temporal 在 workflow handle 物件中有提供以下方法可以獲得當前指定 workflow 的相關資訊狀態

  • Workflow Handle API methods
  • WorkflowExecutionStatus 使用這個變數會需要先下載 @temporalio/proto 這個擴充包喔

下面範例是如何在當前 workflow 中取消自己的範例,例如結束 cronSchedule 的情況,因為 workflow 環境無法使用 client 工具,必須拉出來到 activity 中使用

// my-activities.ts
import { temporal } from '@temporalio/proto';
import { getClient } from './client';

const { WorkflowExecutionStatus } = temporal.api.enums.v1;

export async function terminateWorkflow(workflowId: string) {
  const client = await getClient();
  const wfHandle = client.getHandle(workflowId);
  // 取得 workflow handle 的當前狀態
  const { status } = await wfHandle.describe();
  // 判斷 code 狀態是否處在 running
  if (status.code === WorkflowExecutionStatus.WORKFLOW_EXECUTION_STATUS_RUNNING) {
    // 終止 cronSchedule,若使用 cancel 只會停止當前的 workflow
    // https://docs.temporal.io/concepts/what-is-a-temporal-cron-job/#how-to-stop-a-temporal-cron-job
    await wfHandle.terminate();
  }
}
// workflow.ts
import { proxyActivities, workflowInfo } from '@temporalio/workflow';
import * as myActivities from './myActivities';

const { terminateWorkflow } = proxyActivities<typeof myActivities>({
  startToCloseTimeout: '30s',
});

export async function TestWorkflow() {
  const { workflowId } = workflowInfo();
  // ...
  await terminateWorkflow(workflowId);
}

今天就介紹到這拉,後續如果有其他新的想法也會持續更新紀錄在這,感謝收看,下次再見 =V=

Share On:

FacebookLINEMessengerTelegram

Reference

  • Intro to Temporal with TypeScript SDK + Q&A
  • Temporal Docs
  • Temporal Typescript Reference
  • What is a Signal
  • WorkflowIdReusePolicy
  • RetryPolicy
最近更新: 2025/6/1 下午2:35
Contributors: JohnnyWang, Lindy Liao
Prev
一起動手用 Socket.io 和 Peerjs 打造 WebRTC 即時視訊
Next
React Web3 Storage