如何通过GenServer的cast异步调用API并合并10次响应为元组列表?
用GenServer异步调用API并收集多次响应的实现方案
你想用GenServer的cast异步调用API 10次,还要把所有响应收集成元组列表对吧?核心思路是用GenServer的状态来跟踪请求进度——毕竟cast是无返回的,得靠状态存已完成的响应、计数,等全部请求结束后再汇总结果。下面给你一步步拆解实现:
1. 调整GenServer的状态结构
首先得在初始化时给状态加上跟踪所需的字段:总请求数、已完成请求数、收集到的响应列表,还有发起请求的调用者PID(方便最后把结果返回给调用方):
defmodule DataMonitor.RuleReceiver do use GenServer require Logger alias DataMonitor.ProcessRuleSet # 初始化状态:总请求数、已完成数、响应列表、调用者PID def init(_state) do {:ok, %{total: 0, done: 0, responses: [], caller: nil}} end def start_link(opts \\ []) do GenServer.start_link(__MODULE__, [], opts) end
2. 修改调用入口函数
原来的process_rule_set需要明确传入总请求次数(这里是10次),还要带上调用者的PID,这样GenServer完成所有请求后能把结果发回来:
# 调用方用这个函数启动10次异步API请求 def process_rule_set(receiver_pid, {rule_set, company_id, auth_headers}) do # 获取调用者PID,方便后续返回结果 caller_pid = self() # 设定总请求次数为10 total_requests = 10 # 先给GenServer发消息初始化请求参数 GenServer.cast(receiver_pid, {:init_requests, total_requests, caller_pid, rule_set, company_id, auth_headers}) end
3. 实现handle_cast处理逻辑
接下来要处理两种cast消息:一种是初始化请求参数并批量触发10次API调用;另一种是处理单个API调用的结果,更新状态并判断是否全部完成:
# 初始化请求,触发10次异步API调用 def handle_cast({:init_requests, total, caller, rule_set, company_id, auth_headers}, state) do # 更新状态,记录总请求数和调用者PID new_state = %{state | total: total, caller: caller} # 循环触发10次API调用(用Task单独开进程,避免阻塞GenServer) Enum.each(1..total, fn _ -> Task.start(fn -> # 假设你的API调用逻辑在ProcessRuleSet.call_api里 response = ProcessRuleSet.call_api(rule_set, company_id, auth_headers) # API调用完成后,给GenServer发cast消息返回结果 GenServer.cast(self(), {:api_response, response}) end) end) {:noreply, new_state} end # 处理单个API的响应,更新状态 def handle_cast({:api_response, response}, state) do # 累加已完成次数,把响应包装成元组加入列表(元组格式可按需调整) new_done = state.done + 1 new_responses = [{response, :success} | state.responses] # 检查是否所有请求都完成了 if new_done == state.total do # 反转列表保证顺序和发起请求的顺序一致 final_responses = Enum.reverse(new_responses) # 给调用者发送最终汇总结果 send(state.caller, {:all_responses, final_responses}) # 重置状态,方便下次复用 {:noreply, %{state | done: 0, responses: []}} else # 还没完成,更新状态继续等待 {:noreply, %{state | done: new_done, responses: new_responses}} end end
4. 调用方接收结果的方式
调用方在启动请求后,需要监听消息来获取最终的响应列表:
# 调用方代码示例 {:ok, receiver_pid} = DataMonitor.RuleReceiver.start_link() DataMonitor.RuleReceiver.process_rule_set(receiver_pid, {rule_set, company_id, auth_headers}) # 监听结果消息,设置超时避免无限等待 receive do {:all_responses, responses} -> # 这里拿到的就是10次API响应组成的元组列表 IO.inspect(responses, label: "所有API响应汇总") after 30_000 -> IO.puts("请求超时,请检查API响应速度") end
几个关键细节说明
- 用Task异步执行API调用:如果直接在
handle_cast里调用同步API,会阻塞GenServer进程,导致其他消息无法处理。用Task.start把API调用放到单独进程,能保证GenServer的响应性。 - 状态的原子性更新:GenServer的状态更新是串行的,不用担心多进程更新状态的竞态问题,能确保每个响应都被正确计数和收集。
- 结果通知机制:通过
send给调用者PID发送消息,是Elixir里异步场景下传递结果的常用方式,简单直接。
内容的提问来源于stack exchange,提问作者Jagdish Narayandasani
相关产品推荐
相关产品推荐

