【问题标题】:How to get Enum.reduce until list get empty Elixir如何获取 Enum.reduce 直到列表为空 Elixir
【发布时间】:2019-03-08 03:16:26
【问题描述】:

我正在尝试从 Intercom 获取所有公司列表。对于per_page,它一次不会超过 60 个。还有另一个端点 https://api.intercom.io/companies/scroll,这得到scroll_param 得到其他公司等等。

滚动参数随第一个请求一起提供,然后您可以使用它,直到公司变为[]

我已经创建了这个方法

  defp companies(scroll_param) do
    url = if scroll_param, do: "#{@intercom_url}/companies/scroll?scroll_param=#{scroll_param}", else: "#{@intercom_url}/companies/scroll"
    headers = ["Authorization": "Bearer #{@intercom_token}", "Accept": "Accept:application/json"]
    response = HTTPoison.get(url, headers) |> elem(1)
    case response.status_code do
      200 -> {:ok, response}
      _ -> {:error, response}
    end
  end

我正在尝试这样做

companies = companies(nil)
scroll_param = companies["scroll_param"]
all_companies =
  Enum.reduce(companies["companies"], [], fn company, acc ->
    if companies["companies"] == [] || is_nil(companies["companies"]) do
      {:halt, acc}
    else
      acc ++ company["companies"]
    end
  end)

首先,获取所有具有 nil scroll_param 的公司,然后执行 Enum.reduce 直到公司变为 []

当没有公司可以显示时,请求给出

%{"type":"company.list","companies":[],"scroll_param":"b85a7745-d423-49bf-91b2-72a513b781e4"}

我的问题是我不知道如何在这里使用递归,所以我可以让所有公司都以 all_companies 的形式加入变量中

第一个第一个请求给出这样的值

%{ "companies": [{ "type":"company",
  "company_id":"smithbrothersltd.co.uk",
  "id":"5c7817185170c3ed1cf9d07a",
  "app_id":"f9c1fd60de50d31bcbc3f4d8d74c9c6dbc40e95a",
  "name":"Smith Brothers",
  "remote_created_at":1551374104,
  "created_at":1551374104,
  "updated_at":1551795637,
  "last_request_at":1551795636,
  "monthly_spend":0,
  "session_count":3,
  "user_count":6,
  "tags":{"type":"tag.list","tags":[]},
  "segments":{"type":"segment.list","segments":[{"type":"segment","id":"53834904c1bbf82df800b256"}]},"plan":{},"custom_attributes":{"creation_source":"api"}}
  ],
  "scroll_param":"b85a7745-d423-49bf-91b2-72a513b781e4"
  }

【问题讨论】:

    标签: elixir


    【解决方案1】:

    这可能会作为一个 gen_server 更好地完成,它可以在发生错误(退避、重试等)的情况下控制请求流,但这意味着知道它在哪里使用以及如何使用。我不认为使用Enum.reduce 比仅声明一个执行此操作的模块有任何好处。您可能想要辨别除 200 之外的错误和状态代码,并明确决定是否值得重试,或记录错误/状态代码等。因为如果出现错误,它将机枪 5 次,在第 5 次失败连续错误或非200 响应代码。我假设 HTTPoison 在提供标头时将正文解码为 JSON,否则您还需要为响应添加解码步骤。

    defmodule MyApp.Intercom do
    
      @intercom_scroll_url @intercom_url <> "/companies/scroll?scroll_param="
      @intercom_base_url   @intercom_url <> "/companies/scroll"
      @intercom_headers    ["Authorization": "Bearer #{@intercom_token}", "Accept": "Accept:application/json"]
    
      @max_retries 5
    
      alias HTTPoison.Response, as: Resp
    
      def get_companies(acc \\ [], scroll_param \\ nil, errors \\ [], retries \\ 0)
      def get_companies(acc, scroll_param, errors, retries) when retries < @max_retries do
        case get_companies_request(scroll_param) do
          {:ok, %Resp{status_code: 200, body: %{"companies" => []}}} ->
            {:ok, :lists.flatten(acc)}
    
          {:ok, %Resp{status_code: 200, body: %{"companies" => companies, "scroll_param" => n_scroll_param}}} ->
            get_companies([companies | acc], n_scroll_param, errors, 0)
    
          {:ok, %Resp{status_code: sc, body: body}} ->
            {:error, {:unexpected_status_code, {sc, body}}}
    
          {:error, %HTTPoison.Error{reason: reason}} ->
            get_companies(acc, scroll_param, [reason | errors], retries + 1)
        end
      end
    
      def get_companies(acc, _scroll_param, errors, _), do: {:error, {errors, acc}}
    
      defp get_companies_request(nil), do: HTTPoison.get(@intercom_base_url, @intercom_headers)
      defp get_companies_request(sp), do: HTTPoison.get(@intercom_scroll_url <> sp, @intercom_headers)
    end
    
    MyApp.Intercom.get_companies()
    

    【讨论】:

    • 它只给了 100 家公司。而 229 可用。您认为可能是什么问题?
    • 还有如何使用scroll_params?
    • 也是,它不解码 JSON,而且你的 scroll_param 总是空的
    • 它应该解码 JSON 否则它将匹配第 3 个子句。至于scroll_param 总是为空,这是因为代码原样没有在第二个子句中提取它,您可以这样做并替换scroll_param 那里
    【解决方案2】:

    如果scroll_param 在所有请求中保持相同(当然第一个请求除外),那么

    all_companies = [scroll_param]  # The scroll param acquired by the first request
                    |> Stream.cycle()
                    |> Stream.map(&companies/1)
                    |> Stream.take_while(fn 
                         {:ok, %{"companies" => []}} -> false
                         {:ok, _} -> true
                         {:error, _} -> false
                       end)
                    |> Enum.map(fn{:ok, %{"companies" => companies}} -> companies end)
    

    然后,您在第一个请求获取的公司前面加上List.flatten 结果。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-01-21
      • 2020-01-20
      • 2015-12-26
      • 1970-01-01
      • 1970-01-01
      • 2018-06-22
      相关资源
      最近更新 更多