Async обещает рекурсию с неизвестной подачей асинхронных значений - PullRequest
0 голосов
/ 07 октября 2019

Я бьюсь головой об асинхронные обещания рекурсии. У меня есть куча обещаний, которые разрешаются при загрузке асинхронных данных (в сочетании с Promise.all). Но иногда в данных, которые я просто загружаю, есть ссылка на другие данные, которые необходимо загрузить (рекурсия). Лучшее объяснение показывает код, который я думаю. Комментарии в коде. (Я пробовал разные комбинации безрезультатно.)

var urls = ['http://czyprzy.vdl.pl/file1.txt', 'http://czyprzy.vdl.pl/file2.txt', 'http://czyprzy.vdl.pl/file3.txt'];
var urlsPromise = [];
var secondPart = [];
var thirdPart = [];

function urlContent(url, number) {
  return new Promise(function (resolve) {
    var dl = request(url, function (err, resp, content) {
      if (err || resp.statusCode >= 400) {
        return resolve({number : number, url : url, error : 'err'});
      }
      if (!err && resp.statusCode == 200) {
        if (content.indexOf('file') !== -1) // if there is 'file' inside content we need (would like to :) download this new file by recursion
        {
            content = content.slice(content.indexOf('file') + 4);
            content =+ content; // (number to pass later on, so we know what file we are working on)
            url = 'http://czyprzy.vdl.pl/file' + content + '.txt'; // (we build new address)

            //urlsPromise.push(urlContent(url, content)); // this will perform AFTER Promise.all(urlsPromise) so we simply can't do recurention (like that) here
            secondPart.push(urlContent(url, content)); // if we use another promise array that put resolved items to that array everything will work just fine - but only till first time, then we would need to add another (thirdPart) array and use another Promise.all(thirdPart)... and so on and so on... --- the problem is I don't know how many files there will be, so it means I have no idea how many 'parts' for Promise.all I need to create, some kind of asynchronous loop/recursion would save me here, but I don't know how to do that properly so the code can run in proper order
        }
        return resolve({number : number, url : url}); // this goes to 'urlsPromise' array
      }
    });
  });
}

if (urls.length !== 0) {
  for (var i = 0; i < urls.length; i++)
  {urlsPromise.push(urlContent(urls[i], i + 1));}
}

Promise.all(urlsPromise).then(function(urlsPromise) {
  console.log('=======================================');
  console.log('urlsPromise:\n');
  console.log(urlsPromise); // some code/calculations here
}).then(function() {
  return Promise.all(secondPart).then(function(secondPart) {
    console.log('=======================================');
    console.log('secondPart:\n');
    console.log(secondPart); // some code/calculations here
    secondPart.forEach(function(item)
    {
        thirdPart.push(urlContent(item.url, item.number + 3));
    });
  });
}).then(function() {
  return Promise.all(thirdPart).then(function(thirdPart) {
    console.log('=======================================');
    console.log('thirdPart:\n');
    console.log(thirdPart); // some code/calculations here
  });
}).then(function()
{
    console.log();
    console.log('and so on and so on...');
});



//// files LINKING (those files do exist on live server - just for testing purposes):
// file1->file4->file7->file10  /-/ file1 content: file4 /-/ file4 content: file7 /-/ file7 content: file10
// file2->file5->file8->file11  /-/ file2 content: file5 /-/ file5 content: file8 /-/ file8 content: file11
// file3->file6->file9->file12  /-/ file3 content: file6 /-/ file6 content: file9 /-/ file9 content: file12



//// the console.log output looks like this:
// =======================================
// urlsPromise:

// [ { number: 1, url: 'http://czyprzy.vdl.pl/file4.txt' },
//   { number: 2, url: 'http://czyprzy.vdl.pl/file5.txt' },
//   { number: 3, url: 'http://czyprzy.vdl.pl/file6.txt' } ]
// =======================================
// secondPart:

// [ { number: 4, url: 'http://czyprzy.vdl.pl/file7.txt' },
//   { number: 5, url: 'http://czyprzy.vdl.pl/file8.txt' },
//   { number: 6, url: 'http://czyprzy.vdl.pl/file9.txt' } ]
// =======================================
// thirdPart:

// [ { number: 7, url: 'http://czyprzy.vdl.pl/file10.txt' },
//   { number: 8, url: 'http://czyprzy.vdl.pl/file11.txt' },
//   { number: 9, url: 'http://czyprzy.vdl.pl/file12.txt' } ]

// and so on and so on...

Ответы [ 2 ]

0 голосов
/ 03 ноября 2019

Этот ответ был получен благодаря 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 () .

0 голосов
/ 08 октября 2019

Ключевое слово await может значительно упростить это. Вам не нужно использовать саморекурсивную функцию. Эта демонстрация подделывает вызов сервера с помощью массива произвольного размера.

https://jsfiddle.net/mvwahq19/1/

// setup: create a list witha  random number of options.
var sourceList = [];
var numItems = 10 + Math.floor(Math.random() * 20);

for (var i = 0; i < numItems; i++)
{
    sourceList.push(i);
}

sourceList.push(100);

var currentIndex = 0;

// a function which returns a promise. Imagine it is asking a server.
function getNextItem() {
    var item = sourceList[currentIndex];
    currentIndex++;
    
    return new Promise(function(resolve) {
        setTimeout(function() {
          resolve(item);
        }, 100);
    });
}

async function poll() {
    var collection = [];
    var done = false;
    while(!done) {
        var item = await getNextItem();
        
        collection.push(item);
        
        console.log("Got another item", item);
        
        if (item >= 100) {
            done = true;
        }
    }
    console.log("Got all items", collection);
}

poll();

Вы можете написать нормальный цикл for, за исключением использования содержимого await.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...