如何用PureScript改写Node.js异步TypeScript脚本?
如何用PureScript的FFI调用现有Node.js/TypeScript代码并通过do语法处理异步流程?
我正在学习PureScript,发现现有文献大多聚焦于抽象概念——虽然PureScript对这些概念的适配性极佳,但我很难把它们和熟悉的实践结合起来。为了更好地理解PureScript并验证它在过程性任务中的表现,我希望改写一段简易的Node.js TypeScript脚本。
原脚本的功能是:读取命令行输入,调用异步函数hydrate服务端redux store,随后在控制台打印数据。原TypeScript代码如下:
import { store } from '../server/store' import { deep, log, red } from '../src/io' import { isRehydrated } from './isRehydrated' async function readRecord(dbName: string, tableName: string, recordId: string) { try { await isRehydrated() const result = store.getState().databases[dbName][tableName][recordId] deep(result) return 'DONE!' } catch (e) { return e } } readRecord(process.argv[2], process.argv[3], process.argv[4]).then( m => { log(m); process.exit() }, e => { red(e); process.exit() } )
请问有没有简便方法,使用PureScript的FFI调用上述JavaScript导入,并通过do语法处理异步流程来改写该脚本?
当然有!我们可以分两步完成:创建FFI绑定来桥接你的JavaScript模块,然后用PureScript的Aff类型(支持do语法的异步抽象)编写核心逻辑。下面是具体实现:
1. 创建FFI绑定文件
首先,创建一个PureScript能调用的JavaScript绑定文件(比如src/Node/StoreFFI.js),用来导入你的现有JS/TS模块并暴露给PureScript:
// src/Node/StoreFFI.js import { store } from '../server/store'; import { deep, log, red } from '../src/io'; import { isRehydrated } from './isRehydrated'; // 暴露store的getState方法 export const getStoreState = () => store.getState(); // 暴露控制台输出函数 export const logImpl = log; export const redImpl = red; export const deepImpl = deep; // 暴露异步的isRehydrated函数 export const isRehydratedImpl = isRehydrated;
然后,创建对应的PureScript模块(src/Node/StoreFFI.purs)来定义类型和FFI绑定:
-- src/Node/StoreFFI.purs module Node.StoreFFI where import Prelude import Effect (Effect) import Effect.Aff (Aff) -- 定义store状态的类型(可以根据实际结构细化) foreign import data StoreState :: Type -- 绑定getState方法 foreign import getStoreState :: Effect StoreState -- 绑定控制台输出函数 foreign import logImpl :: String -> Effect Unit foreign import redImpl :: String -> Effect Unit foreign import deepImpl :: forall a. a -> Effect Unit -- 绑定异步的isRehydrated函数 foreign import isRehydratedImpl :: Aff Unit
2. 编写PureScript核心逻辑
接下来,创建主模块(比如src/Main.purs),用do语法处理异步流程,读取命令行参数,调用FFI绑定:
-- src/Main.purs module Main where import Prelude import Effect (Effect) import Effect.Aff (Aff, launchAff_, liftEffect) import Node.Process (argv, exit) import Node.StoreFFI (getStoreState, logImpl, redImpl, deepImpl, isRehydratedImpl) import Data.Maybe (Maybe(..), fromMaybe) import Effect.Exception (catch) -- 从命令行参数中获取dbName、tableName、recordId getArgs :: Effect (String, String, String) getArgs = do args <- argv let dbName = fromMaybe "" $ args !! 2 tableName = fromMaybe "" $ args !! 3 recordId = fromMaybe "" $ args !! 4 pure (dbName, tableName, recordId) -- 核心异步逻辑,用do语法编写 readRecord :: String -> String -> String -> Aff String readRecord dbName tableName recordId = do -- 等待store完成hydrate isRehydratedImpl -- 获取store状态 state <- liftEffect getStoreState -- 提取目标数据(若结构复杂,建议用purescript-foreign做类型安全解码) let result = state.databases[dbName][tableName][recordId] -- 打印结果 liftEffect $ deepImpl result pure "DONE!" -- 主函数:启动异步流程,处理结果或错误 main :: Effect Unit main = launchAff_ do (dbName, tableName, recordId) <- liftEffect getArgs catch (do msg <- readRecord dbName tableName recordId liftEffect $ logImpl msg) (\err -> liftEffect $ redImpl $ show err) -- 退出进程 liftEffect $ exit 0
关键说明
- FFI绑定:我们把JS模块中的函数/值包装成PureScript能识别的类型,确保类型安全。如果你的
StoreState结构复杂,推荐用purescript-foreign或purescript-codecs做解码,避免直接访问原生JS对象带来的风险。 - Aff与do语法:
Aff是PureScript中用于异步操作的monad,完美支持do语法,让异步代码的结构和同步代码一样清晰直观。launchAff_用来把Aff异步流程转换成可执行的Effect。 - 错误处理:用
catch函数捕获异步流程中的错误,对应原TS代码里的try/catch逻辑,能统一处理异常场景。
最后,记得在你的spago.dhall中添加必要的依赖:effect、aff、node-process这些常用库即可。
内容的提问来源于stack exchange,提问作者RichardForrester
相关产品推荐
相关产品推荐

