Синхронные множественные запросы в цикле foor с Node js
Я начинаю с Javascript, и мне нужна помощь, чтобы понять, как сделать этот код синхронным во время цикла for. По сути, я делаю несколько POST-запросов внутри для циклов, а затем я очищаю данные с помощью библиотеки X-Ray, и, наконец, я сохраняю результат в базе данных Mongo. Вывод в порядке, но он поступает неупорядоченным образом и неожиданно зависает, и мне приходится принудительно закрывать клавишей Ctrl+C. Это моя функция:
function getdata() {
const startYear = 1996;
const currentYear = 1998; // new Date().getFullYear()
for (let i = startYear; i <= currentYear; i++) {
for (let j = 1; j <= 12; j++) {
if (i === startYear) {
j = 12;
}
// Form to be sent
const form = {
year: `${i}`,
month: `${j}`,
day: '01',
};
const formData = querystring.stringify(form);
const contentLength = formData.length;
// Make HTTP Request
request({
headers: {
'Content-Length': contentLength,
'Content-Type': 'application/x-www-form-urlencoded',
},
uri: 'https://www.ipma.pt/pt/geofisica/sismologia/',
body: formData,
method: 'POST',
}, (err, res, html) => {
if (!err && res.statusCode === 200) {
// Scrapping data with X-Ray
x(html, '#divID0 > table > tr', {
date: '.block90w',
lat: 'td:nth-child(2)',
lon: 'td:nth-child(3)',
prof: 'td:nth-child(4)',
mag: 'td:nth-child(5)',
local: 'td:nth-child(6)',
degree: 'td:nth-child(7)',
})((error, obj) => {
const result = {
date: obj.date,
lat: obj.lat.replace(',', '.'),
lon: obj.lon.replace(',', '.'),
prof: obj.prof == '-' ? null : obj.prof.replace(',', '.'),
mag: obj.mag.replace(',', '.'),
local: obj.local,
degree: obj.degree,
};
// console.log(result);
upsertEarthquake(result); // save to DB
});
}
});
}
}
}
Я предполагаю, что должен использовать обещания или обратные вызовы, но я не могу понять, как это сделать, и я уже пытался использовать асинхронное ожидание, но безуспешно. Если вам нужна дополнительная информация, пожалуйста, скажите мне, спасибо.
3 ответа
Вы вызываете запрос внутри цикла.
Асинхронные функции - это функции, в которых получение результата (AKA, получение ответа в функции обратного вызова) выполняется после завершения логики основного потока.
Таким образом, если у нас есть это:
for (var i = 0; i < 12; i++) {
request({
data: i
}, function(error, data) {
// This is the request result, inside a callback function
});
}
Логика будет бежать за 12 request
s перед вызовом обратных вызовов, поэтому обратные вызовы будут суммироваться и вызываться после выполнения всего основного цикла.
Не вдаваясь в подробности о генераторах ES6 (поскольку я думаю, что это немного усложняет работу, а также лучше изучать на низком уровне происходящее), вам нужно назвать request
, дождитесь вызова его функции обратного вызова и вызовите следующий request
, Как это сделать? Есть много способов, но я обычно так:
var i= 0;
function callNext() {
if (i>= 12) {
requestEnded();
} else {
request({
data: i++ // Increment the counter as we are not inside a for loop that increments it
}, function(error, data) {
// Do something with the data, and also check if an error was received and act accordingly, which is very much possible when talking about internet requests
console.log(error, data);
// Call the next request inside the callback, so we are sure that the next request is ran just after this request has ended
callNext();
})
}
}
callNext();
requestEnded() {
console.log("Yay");
}
Здесь вы видите логику. У вас есть функция с именем callNext
который сделает следующий звонок или звонок requestEnded
если звонки больше не нужны.
когда request
называется внутри callNext
, он будет ожидать получения обратного вызова (что произойдет асинхронно, когда-нибудь в будущем), обработает полученные данные, а затем внутри обратного вызова вы скажете ему повторить вызов callNext
,
Вместо зацикливания вы можете создать массив, используя начальный и конечный год, затем сопоставить его с конфигурацией для вашего запроса, а затем сопоставить результат этого с тем, что возвращает рентген (рентгеновский снимок возвращает обещание, так что нет необходимости в обратном вызове). Затем поместите результат очистки в mongodb, используя функцию, которая возвращает обещание.
Если что-то отвергает, создайте Fail
введите объект и разрешите с этим объектом.
Запустите все запросы, рентген и монго параллельно с помощью Promise.all, но ограничьте количество активных запросов с помощью газа.
Вот как это будет выглядеть в коде:
//you can get library containing throttle here:
// https://github.com/amsterdamharu/lib/blob/master/src/index.js
const lib = require('lib');
const Fail = function(details){this.details=details;};
const isFail = o=>(o&&o.constructor)===Fail;
const max10 = lib.throttle(10);
const range = lib.range;
const createYearMonth = (startYear,endYear)=>
range(startYear,endYear)
.reduce(
(acc,year)=>
acc.concat(
range(1,12).map(month=>({year,month}))
)
,[]
);
const toRequestConfigs = yearMonths =>
yearMonths.map(
yearMonth=>{
const formData = querystring.stringify(yearMonth);
return {
headers: {
'Content-Length': formData.length,
'Content-Type': 'application/x-www-form-urlencoded',
},
uri: 'https://www.ipma.pt/pt/geofisica/sismologia/',
body: formData,
method: 'POST',
};
}
);
const scrape = html =>
x(
html,
'#divID0 > table > tr',
{
date: '.block90w',
lat: 'td:nth-child(2)',
lon: 'td:nth-child(3)',
prof: 'td:nth-child(4)',
mag: 'td:nth-child(5)',
local: 'td:nth-child(6)',
degree: 'td:nth-child(7)'
}
);
const requestAsPromise = config =>
new Promise(
(resolve,reject)=>
request(
config,
(err,res,html)=>
(!err && res.statusCode === 200)
//x-ray returns a promise:
// https://github.com/matthewmueller/x-ray#xraythencb
? resolve(html)
: reject(err)
)
);
const someMongoStuff = scrapeResult =>
//do mongo stuff and return promise
scrapeResult;
const getData = (startYear,endYear) =>
Promise.all(
toRequestConfigs(
createYearMonth(startYear,endYear)
)
.map(
config=>
//maximum 10 active requests
max10(requestAsPromise)(config)
.then(scrape)
.then(someMongoStuff)
.catch(//if something goes wrong create a Fail type object
err => new Fail([err,config.body])
)
)
)
//how to use:
getData(1980,1982)
.then(//will always resolve unless toRequestConfigs or createYearMonth throws
result=>{
//items that were successfull
const successes = result.filter(item=>!isFail(item));
//items that failed
const failed = result.filter(isFail);
}
)
Что часто случается с очисткой, так это то, что целевой сайт не позволяет вам делать более x запросов в течение какого-либо периода и начинать заносить в черный список ваш IP и отказываться от услуги, если вы пройдете через это.
Допустим, вы хотите ограничить до 10 запросов в 5 секунд, тогда вы можете изменить приведенный выше код на:
const max10 = lib.throttlePeriod(10,5000);
Остальная часть кода такая же
У тебя есть sync for...loop
с async methods
внутри это проблема.
Чистый способ исправить это с
ES2017
async/await
синтаксис
Предполагая, что вы хотите остановить каждую итерацию после upsertEarthquake(result)
Вы должны изменить свой код на что-то вроде этого.
function async getdata() {
const startYear = 1996;
const currentYear = 1998; // new Date().getFullYear()
for (let i = startYear; i <= currentYear; i++) {
for (let j = 1; j <= 12; j++) {
if (i === startYear)
j = 12;
// Form to be sent
const form = {
year: `${i}`,
month: `${j}`,
day: '01',
};
const formData = querystring.stringify(form);
const contentLength = formData.length;
//Make HTTP Request
await new Promise((next, reject)=> {
request({
headers: {
'Content-Length': contentLength,
'Content-Type': 'application/x-www-form-urlencoded',
},
uri: 'https://www.ipma.pt/pt/geofisica/sismologia/',
body: formData,
method: 'POST',
}, (err, res, html) => {
if (err || res.statusCode !== 200)
return next() //If there is an error jump to the next
//Scrapping data with X-Ray
x(html, '#divID0 > table > tr', {
date: '.block90w',
lat: 'td:nth-child(2)',
lon: 'td:nth-child(3)',
prof: 'td:nth-child(4)',
mag: 'td:nth-child(5)',
local: 'td:nth-child(6)',
degree: 'td:nth-child(7)',
})((error, obj) => {
const result = {
date: obj.date,
lat: obj.lat.replace(',', '.'),
lon: obj.lon.replace(',', '.'),
prof: obj.prof == '-' ? null : obj.prof.replace(',', '.'),
mag: obj.mag.replace(',', '.'),
local: obj.local,
degree: obj.degree,
}
//console.log(result);
upsertEarthquake(result); // save to DB
next() //This makes jump to the next for... iteration
})
})
}
}
}
}
Я предполагаю что upsertEarthquake
является асинхронной функцией или является типом огня и забыть.
Если есть ошибка, вы можете использовать next()
, но если вы хотите разорвать цикл, используйте reject()
if (err || res.statusCode !== 200)
return reject(err)