在管理错误的同时链接REST调用管道

时间:2018-03-12 00:25:03

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

来自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

1 个答案:

答案 0 :(得分:4)

解决这个问题的一个很好的一般方法是包装你的功能&#39;以Choice / Either-like类型返回值,并使用高阶函数将绑定它们放在一起,使 failure 传播/短路并包含一些有意义的数据。 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