繁体   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