Как запустить функцию на основе обещаний последовательно

Я пытаюсь запустить функцию node js Lighthouse последовательно (по одному) с массивом URL-адресов. Моя проблема в том, что всякий раз, когда я перебираю массив, Lighthouse запускает все URL-адреса сразу, что, как мне кажется, проблематично, если у вас очень большой массив URL-адресов.

Код:

for(let url of urls) {        
  function launchChromeAndRunLighthouse(url, opts, config = null) {
    return chromeLauncher.launch({chromeFlags: opts.chromeFlags}).then(chrome => {
      opts.port = chrome.port;
      return lighthouse(url, opts, config).then(results => {
        return chrome.kill().then(() => results.lhr)
      });
    });
  }
}

launchChromeAndRunLighthouse('https://example.com', opts).then(results => {
  // Use results!
});

Пожалуйста помоги! И спасибо за ваше время!


person user2305363    schedule 10.01.2019    source источник
comment
Вы не должны помещать объявление функции в цикл for. Но, несмотря на это, ваш код на самом деле не использует цикл, он просто повторно выбирает 'https://example.com'...   -  person Patrick Roberts    schedule 10.01.2019
comment
Ух ты, грязно, возьми функцию снаружи и рассмотри возможность использования forEach или другого метода массива. Взгляните также на async/await. Вы всегда должны отдавать предпочтение функциям более высокого порядка вместо циклов, таких как for ... of.   -  person squeekyDave    schedule 10.01.2019
comment
@squeekyDave forEach() не собирается исправлять асинхронную итерацию. На самом деле, это затруднит правильную реализацию.   -  person Patrick Roberts    schedule 10.01.2019
comment
@PatrickRoberts Я не говорю, что forEach все исправит, как я уже сказал, рассмотрите другие методы массива. Кроме того, работать с асинхронным кодом намного проще с помощью async/await, поэтому я думаю, что ему следует попробовать это.   -  person squeekyDave    schedule 10.01.2019
comment
Извините, банда... этот код был уничтожен наверху. Я заработал. Я опубликую решение в ближайшее время. Спасибо за ваш вклад!   -  person user2305363    schedule 10.01.2019
comment
Возможный дубликат Использование async/await с циклом forEach   -  person ponury-kostek    schedule 10.01.2019


Ответы (4)


Ваш ответ правильный, но его можно улучшить. Поскольку у вас есть доступ к async и await, вы должны полностью использовать их, чтобы сделать код чище:

async function launchChromeAndRunLighthouse (url, opts, config = null) {
  const chrome = await chromeLauncher.launch({chromeFlags: opts.chromeFlags});
  opts.port = chrome.port;
  const { lhr } = await lighthouse(url, opts, config);
  await chrome.kill();
  return lhr;
}

async function launchAudit (urls) {
  for (const url of urls) {
     const results = await launchChromeAndRunLighthouse(url, opts);
     // Use results!
  };
}

launchAudit(urls);
person Patrick Roberts    schedule 10.01.2019

Я верю, что понял это. То, что я сделал, ниже. Пожалуйста, продолжайте присылать отзывы, если вы считаете, что это неправильно.

function launchChromeAndRunLighthouse(url, opts, config = null) {
  return chromeLauncher.launch({chromeFlags: opts.chromeFlags}).then(chrome => {
        opts.port = chrome.port;
    return lighthouse(url, opts, config).then(results => {
      return chrome.kill().then(() => results.lhr)
    });
  });
};

async function launchAudit(urls) {
  for (let url of urls) {
     await launchChromeAndRunLighthouse(url, opts).then(results => {
       // Use results!
     });
  };
};

launchAudit(urls);
person user2305363    schedule 10.01.2019

Вариант ответа Патрика Робертса (который должен быть принятым ответом).

Мне было интересно, нужно ли убивать хром на каждой итерации.

const lighthouse = require('lighthouse');
const chromeLauncher = require('chrome-launcher');

function launchChromeAndRunLighthouse(sites, opts, config = null) {
    return chromeLauncher.launch({chromeFlags: opts.chromeFlags}).then(chrome => {
    opts.port = chrome.port;
    const siteResults = [];
    return new Promise((resolve, reject) => {

        // batch async functions.
        // C/O https://stackoverflow.com/questions/43082934/how-to-execute-promises-sequentially-passing-the-parameters-from-an-array
        const runBatch = async (iterable, action) => {
            for (const x of iterable) {
                await action(x)
            }
        }

        // func to run lighthouse
        const doLightHouse = (site) => new Promise((resolve, reject) => {
            lighthouse(site, opts, config).then(results => {
                siteResults.push(results.lhr);
                resolve();
            });

        });

        // go go go
        runBatch(sites, doLightHouse).then(d => {
            chrome.kill().then((result) => {
                resolve(siteResults)
            })
        });
    });

});
}

const opts = {
    chromeFlags: ['--show-paint-rects'],
    onlyCategories: ['performance']
};

const sites = ['https://www.example.com', 'https://www.test.com']

launchChromeAndRunLighthouse(sites, opts).then(results => {
   // Use results!
    console.log(results);
});
person Emile    schedule 24.04.2020

Просто для выполнения вашего кода в качестве теста мы будем использовать async/await и IIFE
Затем создадим функцию, которая поместит весь наш запрос в массив неразрешенных промисов, чтобы мы могли использовать его с Promise.all()
Вам нужно переписать код примерно так:

(async() => {
  const promisesToExecute = [];

  const launchChromeAndRunLighthouse = async (url, opts, config = null) => {
     const chrome = await return chromeLauncher.launch({chromeFlags: opts.chromeFlags});
     opts.port = chrome.port;

     promisesToExecute.push(lighthouse(url, opts, config));
  }

  const results = await Promise.all(promisesToExecute);

  for(const result of results) {
    const resolvedResult = await result.kill();
    // here you can access your  results.lhr
    console.log(resolvedResult.lhr);
  }
})()

Обратите внимание, этот код не тестировался, поэтому в результате могут возникнуть проблемы с kill(). Но главная цель — ответить на ваш вопрос и объяснить, как выполнять обещания.
Кроме того, если вы не хотите выполнять все обещания одновременно, вы можете использовать Promise.waterfall с каким-нибудь пакетом npm, например это

person Grynets    schedule 10.01.2019