Синхронно обрабатывать вложенный массив - PullRequest
0 голосов
/ 16 января 2020

Я выполняю рефакторинг некоторого кода, который сканирует некоторые веб-страницы (удаляя «ад обратного вызова»), и хочу трехсекундную задержку между каждым запросом . Вот функция запроса:

const getHTML = function(page, i) {
    return new Promise(function(resolve, reject) {
        setTimeout(function () {
            api.makeAPIGetRequest(page).then((html) => {
                resolve(html);
            }).catch((err) => {
                reject(err);
            })
        }, i * 3000);
    });
}

Я перебираю массив объектов, затем массив:

let p = [
    {
        location: 'England',
        pages: [1, 3, 5]
    },
    {
        location: 'Scotland',
        pages: [2, 4, 6]
    }
];

Проблема заключается в том, что вывод является случайным (из-за задержки) :

Page 1 - Loaded
Page 2 - Loaded
Page 5 - Loaded
Page 4 - Loaded
Page 3 - Loaded
Page 6 - Loaded

Это должно быть:

Page 1 - Loaded
Page 3 - Loaded
Page 5 - Loaded
Page 2 - Loaded
Page 4 - Loaded
Page 6 - Loaded

Вот мой код:

p.map(async (data) => {
    await crawlLocationPages(data);
})

function crawlLocationPages(data) {

    return Promise.all(
        data.pages.map(async (page, i) => {
            await getHTML(page, i).then((html) => { // <-- waits 3 seconds
                console.log('Page ' + page + ' - Loaded' );
            });

        })
    ).then(() => {

    })

};

Я бы предпочел сохранить модель объекта и массива такой, какая она есть.

Любая помощь приветствуется.

Ответы [ 3 ]

2 голосов
/ 16 января 2020

await не работает внутри .map и .forEach, но работает внутри for циклов. И, конечно, он должен быть внутри функции async.

const run = async () => {
    for(let data of p){
        await crawlLocationPages(data);
    }
}

const crawlLocationPages = async data => {
    for(let page of data.pages){
        const html = await getHTML(page);
        console.log('Page ' + page + ' - Loaded - HTML = ', html );
        await pause();
    }
}

const pause = () => new Promise( (resolve, reject) => setTimeout(resolve, 3000) );

run()
0 голосов
/ 16 января 2020

Решено с помощью генераторов ES6 и yield.

function* crawlGenerator() {
    for (let i = 0; i <= (p.length - 1); i++) {

        yield crawlLocationPages(p[i]);
    }

}

let crawl = crawlGenerator();
crawl.next();

function crawlLocationPages(data) {

    return Promise.all(
        data.pages.map(async (page, i) => {
            await getHTML(page, i).then((html) => { // <-- waits 3 seconds
                console.log('Page ' + page + ' - Loaded' );
            });

        })
    ).then(() => {
        crawl.next();
    })

};

Более подробная информация здесь: https://davidwalsh.name/async-generators

0 голосов
/ 16 января 2020

Этот подход может быть менее запутанным, но работает, только если каждый запрос занимает не более 3 секунд.

pages = p.flatMap(location => location.pages);
page = 0;
var interval = setInterval(() => {
  if(page === pages.length){
    clearInterval(interval);
  }
  api.makeAPIGetRequest(pages[page++]).then((html) => {
    console.log('Page ' + page + ' - Loaded' );
  }).catch((err) => {
    console.error(err);
  });
}, 3000);

Или позвоните на следующий номер then

function getPage(pages, i) {
    const ts = Date.now();
    api.makeAPIGetRequest(pages[i++]).then((res)=>{
        console.log(res);
        if(i < pages.length) {
            const delay = Math.max(3000 - (Date.now() - ts), 0);
            setTimeout(getPage(pages, i), delay);
        }        
    })
}

pages = p.flatMap(location => location.pages);
getPages(pages, 0);
...