Я пытаюсь продолжать запрашивать API, пока API продолжает возвращаться. Я использую LaunchLibrary API (https://launchlibrary.net/docs/1.3/api.html), и я пытаюсь запросить запуск с прошлого месяца.
API возвращается, в общей сложности, скажем, 15 результатов, но показывает только первые 10. Чтобы получить следующие пять, вы передаете запрос на смещение и запрос еще раз ("& offset = 10"), что даст вам следующие пять.
То, что я хочу сделать, это продолжить запрос от API, передавая смещение до тех пор, пока счетчик, возвращаемый API, не будет равен нулю. Я пытаюсь выполнить это с помощью javascript Promises, но у меня проблемы.
Вот как выглядит мой сжатый файл узла app.js:
app.get("/recent", function(req, res){
var past_launches = [];
var offset = 0;
var count_return = 0;
var endDate = moment().subtract(1, "days");
var startDate = moment().subtract(1, "months").subtract(1, "days");
do {
var url = "https://launchlibrary.net/1.3/launch?startdate="+startDate.format("YYYY-MM-DD")+"&enddate="+endDate.format("YYYY-MM-DD")+"&offset="+offset+"&mode=verbose";
var past_launch_promise = new Promise(function(resolve, reject) {
request(url, function(err, response, body) {
if(!err && response.statusCode == 200) {
var data = JSON.parse(body);
resolve(data);
} else {
reject(Error(err));
}
});
}).then(function(result) {
count_return = result.count;
offset = count_return;
past_launches.concat(result.launches);
}, function(err) {
console.log(err);
});
} while(count_return >= 10);
res.render("recent",{data:promises, embed:embed, status:status});
});
Я понимаю, в чем проблема: поскольку запрос является асинхронным, он достигнет момента до фактического возвращения, а поскольку count_return изначально равен 0, он просто останавливается, прежде чем что-либо может быть возвращено. Я думал, что используя обещания и функцию.then(), я мог заставить цикл ждать, но это, очевидно, не так.
Это запрос, который я использую (https://launchlibrary.net/1.3/launch?startdate=2018-01-11&enddate=2018-02-10&mode=verbose). Добавив "& offset = 10", вы можете получить следующую страницу запуска. Какой бы эффективный способ решить эту проблему.
Один из подходов состоял бы в использовании рекурсии (как правило, не рекомендуется для неограниченных или больших циклов в JavaScript, поскольку они могут... дождаться его... ошибок ).
const moment = require('moment');
const request = require('request-promise-native');
function getLaunches(startDate = moment().subtract(1, 'months').subtract(1, 'days'), endDate = moment(startDate).add(1, "months"), offset = 0, launches = []) {
const url = 'https://launchlibrary.net/1.3/launch?startdate=${moment(startDate).format('YYYY-MM-DD')}&enddate=${moment(endDate).format('YYYY-MM-DD')}&offset=${offset}&mode=verbose';
return request.get({
uri: url,
json: true
}).then((response) => {
const total = response.total;
launches.push(...response.launches);
if (launches.length < total) {
const nextOffset = offset + response.count;
return getLaunches(startDate, endDate, nextOffset, launches);
}
return launches;
});
}
getLaunches().then((launches) => console.log({
total: launches.length,
launches
}));
Альтернативным подходом было бы использовать async/await (поддерживаемый в узле 8+). Следует отметить, что это все еще экспериментальные функции, однако этот ответ заключается в том, чтобы показать, как они могут сделать асинхронный код более похожим на синхронный код в вашем примере.
const moment = require('moment');
const request = require('request-promise-native');
async function getLaunches(startDate = moment().subtract(1, 'months').subtract(1, 'days'), endDate = moment(startDate).add(1, "months"), offset = 0) {
let total = 0;
const launches = [];
do {
const url = 'https://launchlibrary.net/1.3/launch?startdate=${moment(startDate).format('YYYY-MM-DD')}&enddate=${moment(endDate).format('YYYY-MM-DD')}&offset=${offset}&mode=verbose';
const response = await request.get({
uri: url,
json: true
});
total = response.total;
offset += response.count;
launches.push(...response.launches);
} while (launches.length < total);
return launches;
}
getLaunches().then((launches) => console.log({
total: launches.length,
launches
}));
Вы должны быть в состоянии достичь этого с помощью некоторых обещаний пула оркестровки, которые вы можете получить бесплатно с чем-то вроде es6-prom-pool. Ниже приведен пример того, как можно сделать:
const PromisePool = require('es6-promise-pool')
let count_return = -1;
var promiseProducer = function () {
if (count_return === -1 || count_return >= 10) {
var past_launch_promise = new Promise(function(resolve, reject) {
request(url, function(err, response, body) {
...
});
}).then(function(result) {
count_return = result.count;
...
}, function(err) {
... // you could set count_return to -1 here for an early exit
});
return past_launch_promise; // keep going
}
return null; // we're done, tell the promise pool to stop now
}
const concurrency = 1; // only 1 concurrent promise as we want "synchronous like" processing
// Create a pool.
const pool = new PromisePool(promiseProducer, concurrency);
// Start the pool.
const poolPromise = pool.start();
// Wait for the pool to settle.
poolPromise.then(function () {
res.render ...;
}, function (error) { ... });
await
, трюк не является использоватьwhile
цикл, но использовать (псевдо-) рекурсию, где вы неоднократно вызывать функцию , которая выполняет одну единицу работы внутри своей собственной функции обратного вызова , пока никакие единицы не осталось сделать.async
/await
(который является просто синтаксическим сахаром для Promises) может заставить его выглядеть синхронным, что, вероятно, и является причиной этого заблуждения. из