Этот ответ был получен благодаря trincot - https://stackoverflow.com/users/5459839/trincot
Когда я задал ему этот вопрос напрямую, он поддержал меня временем и знаниями и дал этот превосходный ответ.
КОД:
//// files LINKING (those files do exist on live server - just for testing purposes):
// file1->file4(AND file101)->file7->file10 /-/ file1 content: file4 /-/ file4 content: file7 /-/ file7 content: file10 /-/ file10 content: EMPTY /-/ file101 content: EMPTY
// file2->file5(AND file102)->file8->file11 /-/ file2 content: file5 /-/ file5 content: file8 /-/ file8 content: file11 /-/ file11 content: EMPTY /-/ file102 content: EMPTY
// file3->file6(AND file103)->file9->file12 /-/ file3 content: file6 /-/ file6 content: file9 /-/ file9 content: file12 /-/ file12 content: EMPTY /-/ file103 content: EMPTY
var urls = ['http://czyprzy.vdl.pl/file1.txt', 'http://czyprzy.vdl.pl/file2.txt', 'http://czyprzy.vdl.pl/file3.txt'];
var urlsPromise = [];
function requestPromise(url) {
return new Promise(function(resolve, reject) {
request(url, function (err, resp, content) {
if (err || resp.statusCode != 200) reject(err || resp.statusCode);
else resolve(content);
});
});
}
async function urlContent(url, number) {
var arr = [];
let content = await requestPromise(url);
while (content.indexOf(';') !== -1)
{
var semiColon = content.indexOf(';');
var fileLink = content.slice(content.indexOf('file'), semiColon + 1);
content = content.replace(fileLink, ''); // we need to remove the file link so we won't iterate over it again, we will add to the array only new links
var fileLinkNumber = fileLink.replace('file', '');
fileLinkNumber = fileLinkNumber.replace(';', '');
fileLinkNumber =+ fileLinkNumber;
url = 'http://czyprzy.vdl.pl/file' + fileLinkNumber + '.txt'; // we build new address
arr.push({url, fileLinkNumber});
}
if (content.indexOf('file') !== -1)
{
var fileLinkNumber = content.slice(content.indexOf('file') + 4);
fileLinkNumber =+ fileLinkNumber;
url = 'http://czyprzy.vdl.pl/file' + fileLinkNumber + '.txt';
arr.push({url, fileLinkNumber});
}
var newArr = arr.map(function(item)
{
return urlContent(item.url, item.fileLinkNumber); // return IS important here
});
return [].concat(arr, ...await Promise.all(newArr));
}
async function doing() {
let urlsPromise = [];
for (let i = 0; i < urls.length; i++) {
urlsPromise.push(urlContent(urls[i], i + 1));
}
let results = [].concat(...await Promise.all(urlsPromise)); // flatten the array of arrays
console.log(results);
}
//// this is only to show Promise.all chaining - so you can do async loop, and then wait for some another async data - in proper chain.
var test_a = ['http://czyprzy.vdl.pl/css/1.css', 'http://czyprzy.vdl.pl/css/2.css', 'http://czyprzy.vdl.pl/css/cssa/1a.css', 'http://czyprzy.vdl.pl/css/cssa/2a.css'];
var promisesTest_a = [];
function requestStyle(url)
{
return new Promise(function(resolve, reject)
{
request(url, function(error, response, content)
{
if (response.statusCode === 200 && !error)
{resolve(content);}
else
{reject(error);}
});
});
}
for (var i = 0; i < test_a.length; i++)
{promisesTest_a.push(requestStyle(test_a[i]));}
Promise.all(promisesTest_a).then(function(promisesTest_a)
{
console.log(promisesTest_a);
}).then(function()
{
console.log('\nNow we start with @imports...\n');
}).then(function()
{
return doing();
}).then(function()
{
console.log('ALL DONE!');
});
КОММЕНТАРИЙ:
Сначала объяснение, что такое [...] - деструктурированные параметры покоя (на тот случай, если вы этого не знаете).
var arr = [];
var array1 = ['one', 'two', 'three']
var array2 = [['four', 'five', ['six', 'seven']], 'eight', 'nine', 'ten'];
arr = array1.concat(array2);
console.log(arr); // it does not flattern the array - it just concatenate them (join them together)
console.log('---');
// however
arr = array1.concat(...array2);
console.log(arr); // notice the [...] - as you can see it flatern the array - 'four' and 'five' are pull out of an array - think of it as level up :) remember that it pull up WHOLE array that is deeper - so 'six' and 'seven' are now 1 level deep (up from 2 levels deep, but still in another array).
console.log('---');
// so
arr = [].concat(...arr);
console.log(arr); // hurrrray our array is flat (single array without nested elements)
console.log();
Все файлы (ссылки), которые готовы к загрузке (эти 3 начальных в массиве urls ) загружаются почти сразу (синхронный цикл над массивом, который их содержит - одинпосле другого, но очень быстрого, сразу, потому что мы просто перебираем их синхронно).
Затем, когда у нас есть их содержимое (потому что мы ждем, пока контент не будет загружен, - таким образом, мы получили разрешенные данные обещанияздесь) мы начинаем искать информацию о других возможных URL-адресах (файлах), связанных с тем, который мы уже получили, загружать их (через асинхронную рекурсию).
Когда мы нашли всю информацию о возможных дополнительных URL-адресах / файлахФайлы (представленные в массиве регулярных выражений - соответствует ), мы помещаем его в массив данных (в нашем коде с именем arr ) и загружаем их (благодаря мутации url).
Мы загружаем их, возвращая асинхронную urlContent функцию, которая должна Ожидать для requestPromise обещание (поэтому мы имеемразрешить / отклонить данные в urlContent , поэтому при необходимости мы можем изменить его - создайте правильный URL-адрес для получения следующего файла / содержимого).
И так далее, до тех пор, пока мы не "итерируем"(скачать) поверх всех файлов. Каждый раз, когда вызывается urlContent, он возвращает массив обещаний ( переменная обещаний ), которые изначально ожидают. Когда мы ожидаем Promise.all (обещания), выполнение возобновляется только в том месте, когда ВСЕ эти обещания были разрешены. Итак, на данный момент у нас есть значения для каждого из этих обещаний. Каждый из них является массивом. Мы используем один большой конкат, чтобы объединить все эти массивы в один большой массив, включая элементы arr (нам нужно помнить, что для загрузки из файла, который мы уже скачали, может быть более 1 файла -именно поэтому мы храним значения в массиве данных - с именем arr в коде - в котором хранятся promReques функция разрешенных / отклоненных значений). Этот «большой» массив является значением, с которым разрешается обещание. Вспомните, что это обещание было возвращено уже текущим контекстом функции во время первого ожидания.
Это важная часть - поэтому он (urlContent) возвращает (ожидание) одно обещание иэто (возвращенное) обещание разрешается с массивом в качестве значения. Обратите внимание, что асинхронная функция немедленно возвращает обещание вызывающей стороне, когда встречается первое ожидание. Оператор return в асинхронной функции определяет значение, с которым разрешается возвращаемое обещание. В нашем случае это массив.
Таким образом, urlContent при каждом вызове возвращает обещание - разрешенное значение в массиве - [...] (деструктурированные параметры отдыха - возвращает обещание, которое в конечном итоге разрешается в массив),которая собирается нашей асинхронной , выполняющей функцией (потому что при запуске было запущено 3 URL-адреса - у каждого есть своя собственная функция urlContent ... path), которые собирают (ожидают!) все эти массивы из Promise.all (urlsPromise), и когда они разрешаются (мы ожидаем их разрешения и передачи Promise.all), он «возвращает» ваши данные (переменная результатов). Чтобы быть точным, выполнение возвращает обещание (потому что это асинхронно). Но способ, которым мы называем «делать», показывает, что нас не интересует, к чему относится это обещание, и фактически, поскольку выполнение не имеет оператора возврата, это обещание разрешается как НЕ УКАЗАНО (!). В любом случае, мы не используем его - мы просто выводим результаты на консоль.
Одна вещь, которая может сбить с толку асинхронные функции, заключается в том, что оператор return не выполняется при возврате функции (что в имени, верно !?;). Функция уже вернулась, когда выполнила первое ожидание. Когда в конце концов он выполняет оператор return, он на самом деле не возвращает значение, но разрешает «свое» обещание;тот, который он возвратил ранее. Если мы действительно хотим отделить вывод от логики, мы не должны делать console.log (результаты) там, но возвращать результаты, и тогда, где мы называем выполнение, мы можем делать do.then (console.log);Теперь мы используем обещание, возвращаемое при выполнении!
Я бы зарезервировал глагол " для возврата " для того, что вызывающая сторона функции возвращает из нее синхронно . Я бы использовал « для разрешения » для действия, которое устанавливает обещание для разрешенного состояния со значением , значением, к которому можно получить доступ с помощью await или .then () .