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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 04:28:00