【问题标题】:For loop with fetch returning empty array带有 fetch 的 for 循环返回空数组
【发布时间】:2021-05-21 22:58:14
【问题描述】:

我正在编写一个进行 api 调用的服务器路由。

我需要发出两个不同的提取请求,因为我需要更多信息,这些信息将在第一次提取中出现。

问题是我声明了一个超出承诺范围的变量,并且由于某种原因,我的 res.send 没有等待数组变满。

我需要迭代直到结果 9(我不能使用 DogApi 的预定义过滤器来显示九个结果!)

if (req.query.name) {
    var myRes = [];
    fetch(`https://api.thedogapi.com/v1/breeds/search?name=${req.query.name}&apikey=${key}`)
        .then(r => r.json())
        .then( data => {

            for (let i = 0; i < 8 && i < data.length; i++) {
                fetch(`https://api.thedogapi.com/v1/images/${data[i].reference_image_id
                    }`)
                    .then(r => r.json())
                    .then(datos => {

                        myRes.push({ ...data[i], ...datos });
                    })
            }
        })
        .then(res.send(myRes))
}

感谢您的帮助!

【问题讨论】:

  • 使用 PromiseAll。类似于this
  • 现在是使用async functionawait 或其他Promise 结构的好时机。
  • 第二个.then() 中也没有return 语句,第三个.then() 会产生语法错误。
  • @WeiYan 这很有帮助,但我真的不知道如何在我的代码中实现它..
  • 重新打开,因为这个问题实际上并不是关于从函数返回值。它更多的是关于管理异步操作循环和其他异步编码错误。

标签: javascript express promise fetch


【解决方案1】:

您可以尝试使用Promise.all 将您的fetch 调用数组转换为一个聚合承诺,该承诺在所有响应都到达时解析为一组响应。如果有任何失败,则整个事情都会失败(如果您不想要全有或全无语义,请使用Promise.allSettled)。不要忘记捕捉错误。

虽然代码没有显示,但在调用.json() 之前,请务必检查response.ok 以确保请求确实成功。如果!repsonse.ok 抛出错误并在.catch 块中处理它是一种典型的策略。在fetch 上编写包装器是避免冗长的好主意。

最后,请注意 Array#slice 替换了 for 循环。对于少于 8 个元素的数组,它将在没有问题的情况下尽可能多地切片。

// mock everything
const fetch = (() => {
  const responses = [
    {
      json: async () => 
        [...Array(10)].map((e, i) => ({reference_image_id: i}))
    },
    ...Array(10)
      .fill()
      .map((_, i) => ({json: async () => i})),
  ];
  return async () => responses.shift();
})();
const req = {query: {name: "doberman"}};
const key = "foobar";
const res = {send: response => console.log(`sent ${response}`)};
// end mocks

fetch(`https://api.thedogapi.com/v1/breeds/search?name=${req.query.name}&apikey=${key}`)
  .then(response => response.json())
  .then(data => 
    Promise.all(data.slice(0, 8).map(e =>
      fetch(`https://api.thedogapi.com/v1/images/${e.reference_image_id}`)
        .then(response => response.json())
    ))
  )
  .then(results => res.send(results))
  .catch(err => console.error(err))
;

【讨论】:

    【解决方案2】:

    这是一个 async function 取消await 的示例:

    async function fun(queryName, key){
      const a = [], p, j = [];
      let firstWait = await fetch(`https://api.thedogapi.com/v1/breeds/search?name=${req.query.name}&apikey=${key}`);
      let firstJson = await firstWait.json(); // must be an Array
      for(let i=0,n=8,j,l=firstJson.length; i<n && i<l; i++){
        a.push(fetch('https://api.thedogapi.com/v1/images/'+firstJson[i].reference_image_id));
      }
      p = await Promise.all(a);
      for(let v of p){
        j.push(v.json());
      }
      return Promise.all(j);
    }
    // assumes req, req.query, req.query.name, and key are already defined
    fun(req.query.name, key).then(a=>{
      // a is your JSON Array
    });

    【讨论】:

    • for 循环对眼睛来说太难了。你见过for..of,还是Array.prototype.map
    • @Thankyou,我当然知道这些循环。我的第二个循环是for..of 循环。 for 循环非常易于阅读。 ; 分隔 initial_vars; condition; increment_vars, 分隔 initial_vars,也可以分隔 increment_vars。 OP 需要循环小于8。是的,我本可以在顶部摆脱 , j = [],并像 const j = p.map(o=&gt;o.json()); 那样完成而不是 for..of 循环,但我怀疑它会提高性能,而且这很容易阅读。
    【解决方案3】:

    JSON

    这是我的热门观点:每次想要获取 JSON 时,停止使用像 fetch 这样的低级函数。每次我们想要获取一些 JSON 时,这都会使获取逻辑纠缠不清。编写一次getJSON 并在需要 JSON 的任何地方使用它 -

    const getJSON = s =>
      fetch(s).then(r => r.json())
    
    const data =
      await getJSON("https://path/to/some/data.json")
    
    // ...
    

    URL 和 URLSearchParams

    另一个热门话题:停止手动编写所有 URL。这将 URL 写入/重写与所有 api 访问逻辑纠缠在一起。我们可以设置一次 DogApi 端点,带有一个基本 url 和一个 apikey -

    const DogApi =
      withApi("https://api.thedogapi.com/v1", {apikey: "0xdeadbeef"})
    

    现在,只要我们需要接触那个端点,就可以为我们插入基本 url 和默认参数 -

    const breed = 
      // https://api.thedogapi.com/v1/breeds/search?apikey=0xdeadbeef&name=chihuahua
      await getJSON(DogApi("/breeds/search", {name}))
        
    
    // ...
    

    withApi 有一个简单的实现 -

    const withApi = (base, defaults) => (pathname, params) =>
    { const u = new URL(url) // <- if you don't know it, learn URL
      u.pathname = pathname 
      setParams(u, defaults)
      setParams(u, params)
      return u.toString()
    }
    
    function setParams (url, params = {})
    { for (const [k,v] of Object.entries(params))
        url.searchParams.set(k, v)  // <- if you don't know it, learn URLSearchParams
      return url
    }
    

    你的劳动成果

    现在编写 imagesForBreed 之类的函数以及任何其他涉及 JSON 或 DogApi 的函数都非常简单 -

    async function imagesForBreed (name = "")
    { if (name == "")
        return []
    
      const breed = 
        await getJSON(DogApi("/breeds/search", {name}))
    
      const images =
        data.map(v => getJSON(DogAPI(`/images/${v.reference_image_id}`))
      
      return Promise.all(images)
    }
    

    您的整个 Express 处理程序被简化为一行,无需触摸 .then 或其他费力的 API 配置 -

    async function fooHandler (req, res)
    { 
       res.send(imagesForBreed(req.query.name))
    }    
    

    【讨论】:

      猜你喜欢
      • 2021-05-24
      • 1970-01-01
      • 2016-11-23
      • 1970-01-01
      • 2022-12-12
      • 1970-01-01
      • 2019-12-11
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多