【问题标题】:Async/Await for recursive API Calls异步/等待递归 API 调用
【发布时间】:2021-05-12 18:24:40
【问题描述】:

我正在尝试从分页端点收集所有 ID。 我想等待所有调用完成,但它似乎只完成第一次调用并返回数据和对下一次调用的承诺。

我怎样才能等待所有的电话返回。

const getSelectAllIds = async (url) => {
  let response = await axios.post(url);
  if (response.status === 200 && response.data.next != null) {
    console.log(`calling: ${response.config.url}`);
    return [
      ...response.data.results,
      await getSelectAllIds(response.data.next)
    ];
  }
};

const someFunction = async (url, data) => {
  const selectedIds = await getSelectAllIds(url);
  return selectedIds;
};

【问题讨论】:

  • 你是在问你是怎么做到的,所以someFunction()getSelectAllIds() 不会返回一个承诺?如果这就是你要问的,你不能。所有async 函数都返回一个承诺。该调用者必须使用await.then() 来从promise 中获取值。
  • 是的,我想确保从分页页面收集所有数据,即 selectIds 返回所有数据而不是承诺。听起来我不能为此使用异步/等待,那么在这种情况下
  • 我认为至少 1 个问题是,如果您的结果有 3 页,那么您只会得到 2 页数据。将跳过最后一页。

标签: javascript recursion async-await promise


【解决方案1】:

所有async 函数总是返回一个承诺。所以getSelectAllIds()someFunction() 都会返回一个promise。该调用者必须使用.then()await 来从promise 中获取值。您不能让它们同步返回异步检索的值。 Javascript 不能那样工作。

这就是 nodejs 中异步编码的工作方式。您永远无法将异步值转换为同步值。您必须使用异步机制来使用异步值。对于 Promise,这意味着调用者必须使用 await.then() 来获取值。

getSelectAllIds(someUrl).then(allIDs => {
     console.log(allIDs);
}).catch(err => {
     console.log(err);
});

请注意,如果response.status 不是 200,则不清楚您希望代码做什么。而且,您似乎没有从最后一页收集数据,因为您没有将数据添加到如果没有data.next,则为数组。

【讨论】:

  • 是的,我提取了很多代码,因为我无法显示任何内容,但这就是我想要的。我很困惑,因为我认为async/await 是在没有then 的情况下编写承诺的另一种方式
  • @KyleCalica-St - awaitasync 函数中工作得很好。它暂停函数执行,直到 promise 解决。但是调用者仍然会在遇到第一个await 时立即返回一个异步承诺。 await 你在这里的return [ ...response.data.results, await getSelectAllIds(response.data.next)]; 工作并大大简化了你的代码。这就是你使用它的方式。
  • 但他已经在使用await。那么问题出在哪里?
  • @arminyahya - 问题是 OP 希望 getSelectAllIds() 返回值,而不是返回承诺。但是,async 函数不能这样工作,事实上,您永远不会同步返回异步检索的值。必须使用异步机制(承诺、回调、事件等)将异步值传回给调用者。
  • @jfriend00 感谢您的解释,我相信我现在开始了解更多了。
【解决方案2】:

我想你想要这个:

const getSelectAllIds = async (url) => {
  const response = await axios.post(url);
  if(!response.ok) throw new Error('HTTP error'); // lets not ignore these.
  return [
    ...response.data.results,
    ...(response.data.next ? await getSelectAllIds(response.data.next) : [])
  ];
}

【讨论】:

    【解决方案3】:

    异步生成器

    我将建议一种不同的方法,使用异步生成器。我希望这种方法的好处是不言而喻的 -

    async function post (url)
    { const r = await axios.post(url)
      if (r.status == 200)
        return r.data   // <- resolve data
      else
        throw r         // <- reject
    }
    
    async function* getSelectAllIds (url)
    { if (url == null) return
      let { config, results, next } = await post(url) // <- get
      console.log(`calling: ${config.url}`)           // <- debug output
      yield *results                                  // <- yield each
      for await (const v of getSelectAllIds(next))    // <- recur
        yield v
    }
    
    async function someFunction (url)
    { const r = []                                    // <- empty result
      for await (const v of getSelectAllIds(url))     // <- simple iteration
        r.push(v)                                     // <- collect results
      return r                                        // <- return
    }
    
    // run and catch errors
    someFunction("/my/url").then(console.log, console.error)
    

    演示

    我想以一种您可以在自己的浏览器中验证结果的方式来演示这一点。为此,我们将制作一个假的DB -

    const DB =
      { "/1":
          { config: { url: "/1" }
          , results: [ "foo", "bar" ]
          , next: "/2"
          }
      , "/2":
          { config: { url: "/2" }
          , results: [ "cat", "dog" ]
          , next: "/3"
          }
      , "/3":
          { config: { url: "/3" }
          , results: [ "pea", "yam" ]
          , next: null
          }
      }
    

    接下来我们需要制作FAKE,它是axios.post的替代品-

    async function FAKE (url)      // <- fake axios.post
    { await sleep (1000)           
      if (url in DB)
        return { status: 200, data: DB[url] } // <- found resource
      else
        return { status: 404, data: null }    // <- notfound resource
    }
    

    这取决于一点sleep 函数,来模拟延迟-

    const sleep = ms =>
      new Promise(r => setTimeout(r, ms))
    

    要运行演示,我们只需将axios.post 替换为我们的FAKE -

    async function post (url)
    { const r = await FAKE(url)    // <- fake axios.post
      if (r.status == 200)
        return r.data
      else
        throw r
    }
    
    someFunction("/1").then(console.log, console.error)
    

    展开下面的 sn-p 以在浏览器中验证结果 -

    const sleep = ms =>            // <- sleep for demo
      new Promise(r => setTimeout(r, ms))
    
    async function FAKE (url)
    { await sleep (1000)           // <- simulated delay
      if (url in DB)
        return { status: 200, data: DB[url] }
      else
        return { status: 404, data: null }
    }
    
    async function post (url)
    { const r = await FAKE(url)    // <- fake axios.post
      if (r.status == 200)
        return r.data
      else
        throw r
    }
    
    async function* getSelectAllIds (url)
    { if (url == null) return
      let { config, results, next } = await post(url)
      console.log(`calling: ${config.url}`)
      yield *results
      for await (const v of getSelectAllIds(next))
        yield v
    }
    
    async function someFunction (url)
    { const r = []
      for await (const v of getSelectAllIds(url))
        r.push(v)
      return r
    }
    
    const DB =
      {"/1":{config:{url:"/1"},results:[ "foo","bar" ],next:"/2"},"/2":{config:{url:"/2"},results:[ "cat","dog" ],next:"/3"},"/3":{config:{url:"/3"},results:[ "pea","yam" ],next:null}}
    
    someFunction("/1").then(console.log, console.error)
    calling: /1
    calling: /2
    calling: /3
    => [ "foo", "bar", "cat", "dog", "pea", "yam" ]
    

    没有生成器

    如果您选择不使用异步生成器,您可以编写一个简单的异步函数。这种方法的一个优点是它完全不需要someFunction,但它有一个明显的缺点,即它在您开始使用它们之前等待所有结果 -

    async function getSelectAllIds (url)
    { if (url == null) return []                            // <-
      let { config, results, next } = await post(url)
      console.log(`calling: ${config.url}`)
      return [ ...results, ...await getSelectAllIds(next) ] // <-
    }
    
    getSelectAllIds("/1").then(console.log, console.error)
    
    calling: /1
    calling: /2
    calling: /3
    => [ "foo", "bar", "cat", "dog", "pea", "yam" ]
    

    将此与使用异步生成器的结果进行比较,我们可以在结果异步到达时使用它们 -

    async function someFunction (url)
    { for await (const v of getSelectAllIds(url)) // <- async generator
        console.log(v)
      return "done"
    }
    
    someFunction("/1").then(console.log, console.error)
    
    calling: /1
    foo
    bar
    calling: /2
    cat
    dog
    calling: /3
    pea
    yam
    done
    

    【讨论】:

    • 不适用于我的用例,我需要按照您的建议进行操作并等待它们。我已经接受了一个答案,我现在对这个项目的问题更多的是 rxjs 和 Redux。但这是对生成器的一个很好的解释和展示,我肯定会保存这篇文章!
    • Kyle 很高兴您能从答案中有所收获。第二部分没有生成器,在返回完整的结果数组之前会等待所有生成器。祝你有美好的一天:D
    猜你喜欢
    • 2021-08-28
    • 2015-08-27
    • 2016-02-21
    • 1970-01-01
    • 2020-09-08
    • 2017-10-28
    • 2018-01-30
    • 2022-01-27
    • 2018-03-31
    相关资源
    最近更新 更多