【问题标题】:Chaining REST calls in a pipeline while managing errors在管理错误的同时在管道中链接 REST 调用
【发布时间】:2026-01-22 23:55:02
【问题描述】:

来自 nodejs,我可以使用 Promises and then operator 链接异步事件,我正在尝试探索在惯用的 F# 中是如何完成的。

我试图链接的调用是对某个实体的 HTTP 休息调用,从创建到更新再到上传图像到发布。

函数组合表示一个函数的输出应该与要组合的第二个函数的输入相匹配,在我的例子中,公共输入和输出将是string,即 JSON 序列化字符串作为所有这些函数的输入和输出.

我了解到您可以使用 >> 运算符编写函数。理想情况下,函数不应该抛出错误,但是 IO 会发生一些事情,例如在这种情况下,如果我尝试创建的实体的 id 存在等等。

未知的问题是如果在链式序列中发生错误会发生什么,调用者如何知道哪里出了问题以及描述消息?操作可能会在链序列的中间或接近尾端或开始时失败。

我期望这些函数在出错时停止执行链并将错误消息返回给调用者。错误消息也是一个 JSON string,所以函数的输入和输出之间没有不兼容,所以你知道。

我也看了Choice,但不确定这是否是我应该去的方向。

代码不一定完整,我正在寻找的只是进一步研究以获得答案并可能改进这个问题的方向。这是一些开始的代码。

let create schema =
    // POST request
    """{"id": 1, "title": "title 1"}""" // result output

let update schema =
    // PUT request, update title
    """{"id": 1, "title": "title 2"}""" // output

let upload schema = 
    // PUT request, upload image and add thumbnail to json
    """{"id": 1, "title": "title 2", "thumbnail": "image.jpg"}""" 

let publish schema =
    // PUT request, publish the entity, add url for the entity
    if response.StatusCode <> HttpStatusCode.OK then
        """{"code": "100", "message": "file size above limit"}"""
    else
        """{"id": 1, "title": "title 2", "thumbnail": "image.jpg", "url": "http://example.com/1"}"""

let chain = create >> update >> upload >> publish

编辑 - 尝试

尝试在上传部分参数化图片缩略图

let create (schema: string) : Result<string,string> =
    Ok """{"id": 1, "title": "title 1"}""" // result output

let update (schema: string) : Result<string,string>  =
    Ok """{"id": 1, "title": "title 2"}""" // output

let upload2 (img: string) (schema: string) : Result<string,string> =
    printf "upload image %s\n" img
    let statusCode = HttpStatusCode.OK
    match statusCode with
    | HttpStatusCode.OK -> Ok """{"id": 1, "title": "title 2", "thumbnail": "image.jpg"}"""
    | x -> Error (sprintf "%A happened" x)

let publish (schema: string) =
    let statusCode = HttpStatusCode.InternalServerError
    match statusCode with
    | HttpStatusCode.OK -> Ok """{"id": 1, "title": "title 2", "thumbnail": "image.jpg", "url": "http://example.com/1"}"""
    | _ -> Error """{"code": "100", "message": "couldn't publish, file size above limit"}"""

let chain = create >> Result.bind update >> Result.bind (upload2 "image.jpg") >> Result.bind publish

【问题讨论】:

  • “Promises and then operator”实际上是穷人在 JavaScript 中实现的一元计算(但针对一种特定场景)。猜猜这个想法最初是从哪里来的? :-)
  • :) 是的,它有不足之处,顺便说一下,这是基于给定答案的单子吗?我读过,但仍在努力理解
  • 是的,这或多或少就是 monad。
  • 感谢您的确认,所以换句话说monad是一个接受单个输入并返回单个输出的函数的管道,并且由于任何阶段的错误,控制返回给调用者的错误或成功后的结果?
  • 请不要将您的问题变成讨论区。这不是 * 的格式。如果您有更多问题,请随时单独发布。

标签: error-handling f# method-chaining function-composition


【解决方案1】:

解决此问题的一个很好的通用方法是将函数的返回值包装在类似 Choice/Either 的类型中,并使用高阶函数将它们绑定在一起,以便失败 传播/短路一些有意义的数据。 F# 有一个 Result 类型和一个 bind 函数,可以像这样使用:

type MyResult = Result<string,string>
let f1 x : MyResult = printfn "%s" x; Ok "yep"
let f2 x : MyResult = printfn "%s" x; Ok "yep!"
let f3 x : MyResult = printfn "%s" x; Error "nope :("
let fAll = f1 >> Result.bind f2 >> Result.bind f3

> fAll "howdy";;
howdy
yep
yep!
[<Struct>]
val it : Result<string,string> = Error "nope :("

前两个函数成功,但第三个函数失败,因此您会返回一个 Error 值。

还可以查看Railway-oriented programming 上的这篇文章。

更新以更具体地针对您的示例:

let create (schema: string) : Result<string,string> =
    Ok """{"id": 1, "title": "title 1"}""" // result output
let update (schema: string) : Result<string,string>  =
    Ok """{"id": 1, "title": "title 2"}""" // output
let upload (schema: string) = 
    let statusCode = HttpStatusCode.OK
    match statusCode with
    | HttpStatusCode.OK -> Ok """{"id": 1, "title": "title 2", "thumbnail": "image.jpg"}"""
    | x -> Error (sprintf "%A happened" x)
let publish (schema: string) =
    let statusCode = HttpStatusCode.InternalServerError
    match statusCode with
    | HttpStatusCode.OK -> Ok """{"id": 1, "title": "title 2", "thumbnail": "image.jpg", "url": "http://example.com/1"}"""
    | _ -> Error """{"code": "100", "message": "file size above limit"}"""
let chain =
  create >> Result.bind update >> Result.bind upload >> Result.bind publish

【讨论】:

  • 感谢您的指导,您能否使用我的函数调用将答案附加到解决方案中,我会了解更多。你知道我在发布函数中添加了一个错误条件。
  • 太棒了,谢谢,非常感谢。阅读此docs.microsoft.com/en-us/dotnet/fsharp/language-reference/… 也更有意义。我会尽快回复你
  • 我一直在研究和查看这个create &gt;&gt; Result.bind update,有什么简单的英文解释Result.bind update 的作用吗?请。
  • @Developer11 Result.bind 采用 1) 一个返回 Result&lt;&gt; 的函数和 2) Result&lt;&gt;。如果ResultError,它只是返回它;如果它是Ok,那么它执行给定的函数并返回它的Result
  • 谢谢解释,开始显示真图了,最后忘记上传功能需要另外传一个参数,即要上传的图片缩略图文件路径,let upload (schema: string, image: string)自定义参数化怎么做在这样的链接过程中工作?