簡體   English   中英

異步/等待遞歸 API 調用

[英]Async/Await for recursive API Calls

我正在嘗試從分頁端點收集所有 id。 我想等待所有調用完成,但它似乎只完成第一個調用並將數據和 promise 返回到下一個調用。

我怎樣才能等待所有的電話返回。

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;
};

所有async函數總是返回 promise。 因此getSelectAllIds()someFunction()都將始終返回 promise。 該調用者必須使用.then()await從 promise 獲取值。 您不能讓它們同步返回異步檢索的值。 Javascript 不能那樣工作。

這就是 nodejs 中異步編碼的工作方式。 您永遠無法將異步值轉換為同步值。 您必須使用異步機制來使用異步值。 對於 Promise,這意味着調用者必須使用.then()await來獲取值。

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

請注意,如果response.status不是 200,則不清楚您希望代碼做什么。而且,看起來您沒有從最后一頁收集數據,因為如果有,您不會將數據添加到數組中沒有data.next 。下一個。

我想你想要這個:

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) : [])
  ];
}

異步生成器

我將建議一種不同的方法,使用異步生成器。 我希望這種方法的好處是不言而喻的——

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 function,模擬延遲 -

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)

展開下面的代碼段以驗證瀏覽器中的結果 -

 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" ]

沒有發電機

如果您選擇不使用異步生成器,您可以編寫一個簡單的異步 function。 這種方法的一個優點是它完全跳過了對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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM