HTTP请求链管理模式:多阶外部服务请求的成败处理方案
管理多层链式HTTP请求的实用方法
在Clojure里处理多层依赖的HTTP请求链——尤其是每个步骤都得处理成功/失败两种场景时——很容易写出嵌套杂乱、难以维护的代码。我整理了几个可行的方案,帮你把这类逻辑变得更清晰:
1. 用Either Monad封装错误流(推荐引入cats库)
Monad的核心作用就是把错误处理逻辑从主流程里抽离出来,让你专注于正常的链式调用,同时自动处理失败分支。
先把每个HTTP请求封装成返回Either类型的函数,然后用Monad的绑定操作串起整个流程:
(ns someproject.namespace (:require [clj-http.client :as http] [cats.core :as m] [cats.monad.either :as e])) ;; 封装安全的POST请求,返回Either(成功是right,失败是left) (defn safe-post [url params] (let [resp (http/post url (merge params {:as :json :throw-exceptions false}))] (if (= (:status resp) 200) (e/right resp) (e/left (str "请求失败,状态码: " (:status resp)))))) (defn http-flow [] (m/>>= (safe-post some-url-1 {:form-params {:foo "bar"}}) ;; 第一步成功后,用返回结果发起第二个请求 (fn [resp-1] (safe-post some-url-2 {:form-params {:token (:token (:body resp-1))}})) ;; 第二步成功后,发起第三个请求 (fn [resp-2] (safe-post some-url-3 {:form-params {:data (:data (:body resp-2))}})) ;; 处理最终成功结果 (fn [resp-3] (e/right (:body resp-3))))) ;; 调用后统一处理结果 (let [result (http-flow)] (e/either (fn [error] (println "出错了:" error)) (fn [success-data] (println "成功获取数据:" success-data))))
2. 用Clojure核心宏做条件链式调用(无需第三方库)
如果不想引入额外依赖,可以用cond->或者some->结合状态判断来控制流程,把嵌套的if拆得更扁平:
(ns someproject.namespace (:require [clj-http.client :as http])) (defn http-flow [] (let [resp-1 (http/post some-url-1 {:form-params {:foo "bar"} :as :json :throw-exceptions false})] (cond-> nil ;; 第一步成功,才执行第二步 (= (:status resp-1) 200) (-> (fn [_] (http/post some-url-2 {:form-params {:token (:token (:body resp-1))} :as :json :throw-exceptions false})) ;; 第二步成功,执行第三步 (fn [resp-2] (if (= (:status resp-2) 200) (http/post some-url-3 {:form-params {:data (:data (:body resp-2))} :as :json :throw-exceptions false}) {:error "第二步请求失败"}))) ;; 第一步失败,返回错误信息 :else {:error "第一步请求失败"}))) ;; 调用后处理结果 (let [result (http-flow)] (if (:error result) (println "错误:" (:error result)) (println "成功:" (:body result))))
3. 拆分步骤为独立函数,用reduce链式执行
把每个请求步骤封装成独立的函数,每个函数接收上一步的成功结果,返回{:ok ...}或{:error ...}的结构,然后用reduce来串起整个流程:
(ns someproject.namespace (:require [clj-http.client :as http])) ;; 第一步请求 (defn step-1 [] (let [resp (http/post some-url-1 {:form-params {:foo "bar"} :as :json :throw-exceptions false})] (if (= (:status resp) 200) {:ok (:body resp)} {:error "第一步失败"}))) ;; 第二步请求,依赖第一步的结果 (defn step-2 [step-1-data] (let [resp (http/post some-url-2 {:form-params {:token (:token step-1-data)} :as :json :throw-exceptions false})] (if (= (:status resp) 200) {:ok (:body resp)} {:error "第二步失败"}))) ;; 第三步请求,依赖第二步的结果 (defn step-3 [step-2-data] (let [resp (http/post some-url-3 {:form-params {:data (:data step-2-data)} :as :json :throw-exceptions false})] (if (= (:status resp) 200) {:ok (:body resp)} {:error "第三步失败"}))) (defn http-flow [] (reduce (fn [acc step-fn] (if (:error acc) acc ;; 已经失败,直接传递错误 (step-fn (:ok acc)))) (step-1) [step-2 step-3])) ;; 调用处理 (let [result (http-flow)] (if (:error result) (println "错误:" (:error result)) (println "成功获取数据:" (:ok result))))
4. 用core.async实现异步链式请求(非阻塞场景)
如果你的业务允许非阻塞处理,可以用core.async把每个请求包装成channel,通过go块来链式处理异步结果:
(ns someproject.namespace (:require [clj-http.client :as http] [clojure.core.async :as a :refer [go <!]])) ;; 封装异步POST请求,返回channel (defn async-post [url params] (go (let [resp (http/post url (merge params {:as :json :throw-exceptions false}))] (if (= (:status resp) 200) {:ok (:body resp)} {:error (str "请求失败,状态码: " (:status resp))})))) (defn http-flow [] (go (let [step-1-result (<! (async-post some-url-1 {:form-params {:foo "bar"}}))] (if (:error step-1-result) step-1-result (let [step-2-result (<! (async-post some-url-2 {:form-params {:token (:token (:ok step-1-result))}}))] (if (:error step-2-result) step-2-result (<! (async-post some-url-3 {:form-params {:data (:data (:ok step-2-result))}})))))))) ;; 调用并处理结果 (a/go (let [result (<! (http-flow))] (if (:error result) (println "错误:" (:error result)) (println "成功获取数据:" (:ok result)))))
这些方法都能帮你把多层HTTP请求的逻辑结构化,避免嵌套过深的let和if,让代码更易维护和阅读。
内容的提问来源于stack exchange,提问作者deadghost
相关产品推荐
相关产品推荐

