You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 07:56:45