2017-02-20 2 views
0

Я создаю API, который, когда GET, выполняется серия вызовов API Новостей, заголовки новостей выводятся в гигантскую строку, и эта строка обрабатывается в объект, который должен быть доставлен в wordcloud на интерфейсе. До сих пор я мог использовать _.after underscore и запросить обещание сделать мое приложение до тех пор, пока все вызовы API не завершились до вызова processWordBank(), который берет гигантскую строку и очищает ее до объекта. Однако, как только вызывается processWordBank(), я не понимаю, где находится поток программы. В идеале processWordBank() возвращает obj в cloudObj в маршрутизаторе, так что obj можно передать в res.json() и выплюнуть в качестве ответа. Я считаю, что мое использование _.after поставило меня в странную ситуацию, но это единственный способ, которым я смог получить асинхронные вызовы, прежде чем приступать к следующему желаемому действию. Какие-либо предложения?Как использовать обещания ждать вызовов асинхронного API

(я пытался оставить весь ненужный код, но дайте мне знать, если это недостаточно)

// 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 
} 
+0

вместо 'закончил()' попробовать на самом деле * * возвращающ ** что-то ... возможно 'return finished()' - но тогда вы хотите, чтобы 'getNewsApi' тоже возвращал –

ответ

1

Большая проблема в вашем асинхронном потоке, что вы используете общую переменная sourceString для обработки результатов , Если у вас несколько вызовов на getNewsApi(), ваш результат не предсказуем и не всегда будет таким же, потому что не существует предопределенного порядка, в котором выполняются асинхронные вызовы. Не только это, но вы никогда не перезагружаете его, поэтому все последующие вызовы также будут включать в себя результаты предыдущих вызовов. Избегайте изменения общих переменных в асинхронных вызовах и вместо этого используйте результаты напрямую.

Я был в состоянии использовать _.after и запрос-обещание Underscore, чтобы сделать мое приложение ждать, пока все вызовы API завершили перед вызовом processWordBank()

Хотя было бы возможно использовать _.after, это может быть сделано очень хорошо с обещаниями, и поскольку вы уже используете обещания для своих запросов, это просто вопрос сбора результатов от них. Поэтому, поскольку вы хотите дождаться завершения всех вызовов 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, мы можем просто использовать массив напрямую, поэтому вам не нужно разбирать строку обратно в массив в конце.

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(), выполняет массив всех запросов, которые сами по себе возвращают массив статей. Это массив 2d, но, предположительно, вам нужен 1-й массив со всеми статьями для вашей функции 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 features (без комментариев от сниппетов выше):

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) 
} 
+0

Чувак, это было очень полезно. Помогли обернуть мою голову, используя обещания для вызовов API. Спасибо! – wrobbinz

Смежные вопросы