Прежде всего, добавьте правильную обработку ошибок в getMunicipalityJsonUrls()
и getGelderlandJsonUrls()
. Это означает:
- Проверять параметр
err
везде, где он присутствует, и передавать ошибку обратно вызывающей стороне. - Получать возможные ошибки из
JSON.parse()
- Проверить http statusCode.
Вот этот исправленный код:
function getMunicipalityJsonUrls(provinceName, callback) {
request({ uri: `https://www.gemeentegeschiedenis.nl/provincie/json/${provinceName}` }, (error, response, body) => {
if (err) {
callback(err);
return;
}
if (response.statusCode !== 200) {
callback(new Error(`http status code ${response.statusCode}`));
return;
}
try {
const jsonUrls = JSON.parse(body).map(url => {
let urlSplit = url.split("gemeentenaam");
let jsonUrl = urlSplit[0] + "gemeentenaam/json" + urlSplit[1];
return jsonUrl.replace("http://", "https://");
});
callback(null, jsonUrls);
} catch(e) {
callback(e);
}
});
}
function getGelderlandJsonUrls(callback) {
getMunicipalityJsonUrls("Gelderland", (err, data) => {
if (err) {
callback(err);
} else {
callback(null, data);
}
});
}
Затем в writeToFile()
добавьте обработку ошибок и мониторинг завершения, и я решил обернуть его в обещание, а не простой обратный вызов, потому что я хочу использовать его с некоторыми утилитами, которые работают с обещаниями.
function writeToFile(url) {
return new Promise((resolve, reject) => {
// get name to make each new file unique
var name = url.split("json/")[1];
var fileStream = fs.createWriteStream(`jsonFiles/${name}.json`);
fileStream.on('error', (e) => {
reject(e);
});
var options = {
url: `${url}`,
method: 'GET',
headers: {
'Accept': 'application/json',
'Accept-Charset': 'utf-8',
json: true
}
}
//request the data from the site and download to the file.
request.get(options).pipe(fileStream).on('error', (e) => {
reject(e);
}).on('finish', () => {
resolve(url);
});
});
}
Теперь нам нужно решить, как l oop пройти по всем URL-адресам. Если какой-либо из URL-адресов когда-либо пытался записать в один и тот же файл (если это даже удаленная возможность), то вам нужно сериализовать URL-адреса, чтобы они не могли выполнять более одной асинхронной операции, пытаясь записать в один и тот же файл в в то же время, потому что это просто испортит этот файл. Итак, если бы это было так, вы могли бы сериализовать запись в файл следующим образом:
// option 1 - serialize writing to files
async function getMunicipalityGeoJsonData(req, res) {
//Get all the urls pointing to the JSON data for the province, Gelderland
getGelderlandJsonUrls((err, jsonUrls) => {
if (err) {
console.log(err);
res.sendStatus(500);
} else {
try {
//for all those urls, write the data to files.
for (url of jsonUrls) {
console.log(url);
await writeToFile(url);
}
res.send("All done");
} catch(e) {
console.log(e);
res.sendStatus(500);
}
}
});
}
Если вы абсолютно уверены, что ни один из этих URL-адресов не приведет к записи в тот же файл, то вы можете запускайте N из них в то время, когда вы определяете, какое наименьшее значение N дает вам достойную производительность. Более высокие значения N потребляют больше пиковых ресурсов (памяти и файловых дескрипторов). Более низкие значения N запускают меньше вещей параллельно. Если целевые имена хостов являются одним и тем же сервером, то обычно вы не хотите, чтобы N было больше, чем около 5. Если целевые хосты, с которых вы извлекаете данные, все разные, вы можете поэкспериментировать со значениями N до, возможно, 20.
// option 2 - run N at a time in parallel
function getMunicipalityGeoJsonData(req, res) {
//Get all the urls pointing to the JSON data for the province, Gelderland
getGelderlandJsonUrls((err, jsonUrls) => {
if (err) {
console.log(err);
res.sendStatus(500);
} else {
//for all those urls, write the data to files.
const numConcurrent = 5;
mapConcurrent(jsonUrls, numConcurrent, writeToFile).then(() => {
res.send("All done");
}).catch(err => {
console.log(err);
res.sendStatus(500);
});
}
})
}
Функция mapConcurrent()
взята из этого ответа Promise.all потребляет всю мою оперативную память и работает следующим образом. Он ожидает, что вы передадите ему массив элементов для итерации, максимальное количество, которое вы хотите в полете одновременно, и функцию, которая будет передавать элемент массива и будет возвращать обещание, связанное с ним, когда это будет сделано или возникнет ошибка:
function mapConcurrent(items, maxConcurrent, fn) {
let index = 0;
let inFlightCntr = 0;
let doneCntr = 0;
let results = new Array(items.length);
let stop = false;
return new Promise(function(resolve, reject) {
function runNext() {
let i = index;
++inFlightCntr;
fn(items[index], index++).then(function(val) {
++doneCntr;
--inFlightCntr;
results[i] = val;
run();
}, function(err) {
// set flag so we don't launch any more requests
stop = true;
reject(err);
});
}
function run() {
// launch as many as we're allowed to
while (!stop && inflightCntr < maxConcurrent && index < items.length) {
runNext();
}
// if all are done, then resolve parent promise with results
if (doneCntr === items.length) {
resolve(results);
}
}
run();
});
}
В Bluebird Promise.map()
и в библиотеке Asyn c есть сопоставимые функции.
Итак, используя этот код, вы теперь можете контролировать, сколько ваших запросов / writeToFile () выполняются одновременно, и вы регистрируете и регистрируете все возможные ошибки. Делайте, вы можете настроить, сколько может быть в полете одновременно, для лучшей производительности и минимального использования ресурсов, и, если есть какие-либо ошибки, вы должны регистрировать эти ошибки, чтобы вы могли отлаживать.
Этот код в настоящее время настроен на прекращение обработки любых дальнейших URL-адресов, если получит ошибку. Вы можете изменить это, если хотите перейти к другим URL-адресам, если вы получили ошибку, настроив mapConcurrent()
. Но я все равно должен убедиться, что вы регистрируете любые ошибки, чтобы вы знали, когда они есть, и можете выяснить, почему вы видите ошибки.
Еще одно примечание. Если бы это был мой код, я бы конвертировал все в обещания (без простых обратных вызовов) и использовал бы библиотеку got()
вместо библиотеки , которая сейчас не поддерживается request()
. Я не пишу новый код, используя библиотеку request()
.