簡體   English   中英

如何使用Promise等待異步API調用

[英]How to use promises to wait for async API calls

我正在創建一個API,當執行GET時,將對News API進行一系列調用,將新聞文章標題提取為一個巨大的字符串,然后將該字符串處理為一個對象,以交付給前端的wordcloud。 到目前為止,我已經能夠使用下划線的_.after和request-promise來使我的應用程序等待所有API調用完成,然后再調用processWordBank() ,該方法將巨型字符串並清理為對象。 但是,一旦processWordBank() ,我將不知道程序的流向何處。 理想情況下, processWordBank()將obj返回到路由器中的cloudObj,以便可以將obj傳遞到res.json()並吐出作為響應。 我相信我對_.after使用使我處於一種怪異的境地,但這是我能夠獲得異步調用以完成下一步操作之前的唯一方法。 有什么建議么?

(我已嘗試省略所有不必要的代碼,但請告知是否足夠)

// includes...
var sourceString = ""
// router
export default ({ config }) => {
  let news = Router()
  news.get('/', function(req, res){
    var cloudObj = getSources()
        res.json({ cloudObj })
  })
  return news
}

// create list of words (sourceString) by pulling news data from various sources
function getSources() {
    return getNewsApi()

}
// NEWS API
// GET top 10 news article titles from News API (news sources are determined by the values of newsApiSource array)
function getNewsApi() {
  var finished = _.after(newsApiSource.length, processWordBank)
  for(var i = 0; i < newsApiSource.length; i++) {
    let options = {
      uri: 'https://newsapi.org/v1/articles?source=' + newsApiSource[i] + '&sortBy=' + rank + '&apiKey=' + apiKey,
      json: true
    }
    rp(options)
    .then(function (res) {
      let articles = res.articles // grab article objects from the response
      let articleTitles = " " + _.pluck(articles, 'title') // extract title of each news article
      sourceString += " " + articleTitles // add all titles to the word bank
      finished() // this async task has finished
    })
    .catch(function (err) {
      console.log(err)
    })
    }
}

// analyse word bank for patterns/trends
function processWordBank(){
  var sourceArray = refineSource(sourceString)
  sourceArray = combineCommon(sourceArray)
  sourceArray = getWordFreq(sourceArray)
  var obj = sortToObject(sourceArray[0], sourceArray[1])
  console.log(obj)
  return obj
}

異步流程中的一個大問題是,您使用共享變量sourceString來處理結果。 當您多次調用getNewsApi()您的結果將不可預測,並且將始終不相同,因為沒有異步調用執行的預定義順序。 不僅如此,而且您永遠也不會重置它,因此所有后續調用也將包括先前調用的結果。 避免在異步調用中修改共享變量,而直接使用結果。

我已經能夠使用下划線的_.after和request-promise使我的應用程序等待所有API調用完成之后再調用processWordBank()

盡管可以使用_.after ,但可以通過promise很好地完成此操作,並且由於您已經在請求中使用了promise,因此僅從它們中收集結果即可。 因此,因為您要等到所有API調用完成后才可以使用Promise.all ,因此一旦所有承諾都實現后,它會返回一個承諾,該承諾將使用所有承諾的值的數組進行解析。 讓我們看一個非常簡單的示例,看看Promise.all的工作原理:

 // Promise.resolve() creates a promise that is fulfilled with the given value const p1 = Promise.resolve('a promise') // A promise that completes after 1 second const p2 = new Promise(resolve => setTimeout(() => resolve('after 1 second'), 1000)) const p3 = Promise.resolve('hello').then(s => s + ' world') const promises = [p1, p2, p3] console.log('Waiting for all promises') Promise.all(promises).then(results => console.log('All promises finished', results)) console.log('Promise.all does not block execution') 

現在我們可以修改getNewsApi()以使用Promise.all Promise.all數組是您正在循環中執行的所有API請求。 這將使用Array.protoype.map創建。 而且,除了在_.pluck返回的數組之外創建字符串_.pluck ,我們還可以直接使用該數組,因此您無需在最后將字符串解析回數組。

function getNewsApi() {
  // Each element is a request promise
  const apiCalls = newsApiSource.map(function (source) {
    let options = {
      uri: 'https://newsapi.org/v1/articles?source=' + source + '&sortBy=' + rank + '&apiKey=' + apiKey,
      json: true
    }
    return rp(options)
      .then(function (res) {
        let articles = res.articles
        let articleTitles = _.pluck(articles, 'title')
        // The promise is fulfilled with the articleTitles
        return articleTitles
      })
      .catch(function (err) {
        console.log(err)
      })
  })
  // Return the promise that is fulfilled with all request values
  return Promise.all(apiCalls)
}

然后,我們需要使用路由器中的值。 我們知道,從getNewsApi()返回的getNewsApi()滿足了所有請求的數組,這些請求本身返回了一組文章。 那是一個2d數組,但是大概您想要一個1d數組,其中包含您的processWordBank()函數的所有文章,因此我們可以首先對其進行展平。

export default ({ config }) => {
  let news = Router()
  new.get('/', (req, res) => {
    const cloudObj = getSources()
    cloudObj.then(function (apiResponses) {
      // Flatten the array
      // From: [['source1article1', 'source1article2'], ['source2article1'], ...]
      // To: ['source1article1', 'source1article2', 'source2article1', ...]
      const articles = [].concat.apply([], apiResponses)
      // Pass the articles as parameter
      const processedArticles = processWordBank(articles)
      // Respond with the processed object
      res.json({ processedArticles })
    })
  })
}

最后,需要將processWordBank()更改為使用輸入參數,而不是使用共享變量。 refineSource不再需要,因為您已經傳遞了一個數組(除非您對其進行了其他修改)。

function processWordBank(articles) {
  let sourceArray = combineCommon(articles)
  sourceArray = getWordFreq(sourceArray)
  var obj = sortToObject(sourceArray[0], sourceArray[1])
  console.log(obj)
  return obj
}

作為獎勵,路由器和getNewsApi()可以使用一些ES6功能進行清理(而無需上面片段的注釋):

export default ({ config }) => {
  const news = Router()
  new.get('/', (req, res) => {
    getSources().then(apiResponses => {
      const articles = [].concat(...apiResponses)
      const processedArticles = processWordBank(articles)
      res.json({ processedArticles })
    })
  })
}

function getNewsApi() {
  const apiCalls = newsApiSource.map(source => {
    const options = {
      uri: `https://newsapi.org/v1/articles?source=${source}&sortBy=${rank}&apiKey=${apiKey}`,
      json: true
    }
    return rp(options)
      .then(res => _.pluck(res.articles, 'title'))
      .catch(err => console.log(err))
  })
  return Promise.all(apiCalls)
}

暫無
暫無

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

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