Извлекать данные из обратного вызова, содержащегося в обещании?

#javascript #node.js #promise #bluebird #es6-promise

#javascript #node.js #обещание #bluebird #es6-обещание

Вопрос:

Прямо сейчас у меня есть следующий фрагмент кода:

 const Promise = require('bluebird');
const readFile = Promise.promisify(fs.readFile);
recordPerfMetrics: function(url) {

    var self = this;
    var perf, loadTime, domInteractive, firstPaint;
    var perfData = {};       
    readFile('urls.txt', 'UTF-8').then(function (urls, err) {
        if (err) {
            return console.log(err);
        }

        var urls = urls.split("n");
        urls.shift();

        urls.forEach(function(url) {     
            console.log(url);   
            self.getStats(url).then(function(data) { 
                data = data[0];
                loadTime = (data.loadEventEnd - data.navigationStart)/1000   ' sec';
                firstPaint = data.firstPaint;
                domInteractive = (data.domInteractive - data.navigationStart)/1000   ' sec';

                perfData = {
                    'URL' : url,
                    'firstPaint' : firstPaint,
                    'loadTime' : loadTime,
                    'domInteractive' : domInteractive
                };
                console.log(perfData);
            }).catch(function(error) {
                console.log(error);
            });
        });      

        // console.log(colors.magenta("Starting to record performance metrics for "   url));
        // this.storePerfMetrics();                       
    });    
},

getStats: function(url) {
    return new Promise(function(resolve, reject){
        console.log("Getting data for url: ",url);
        browserPerf(url, function(error, data) {
            console.log("inside browserPerf", url);
            if (!error) {
                resolve(data);
              } else {
                reject(error);
            }
        }, {
            selenium: 'http://localhost:4444/wd/hub',
            browsers: ['chrome']
        });
    });
},
  

По сути, это считывание URL-адресов из файла и затем вызов функции browserPerf, данные которой возвращаются в функции обратного вызова.

console.log("Getting data for url: ",url); Выполняется в том же порядке, что и URL-адреса, которые хранятся в файле,

но console.log("inside browserPerf", url); это не то же самое соединение, что и ожидалось.

Я ожидаю, что порядок URL-адресов будет:

 console.log(url);   
console.log("Getting data for url: ",url);
console.log("inside browserPerf", url);
  

Но по какой-то причине только первые два выполняются по порядку, а третий запускается случайным образом после того, как все они прочитаны.
Есть идеи, что я здесь делаю не так?

Комментарии:

1. Ваш .forEach() цикл выполняется синхронно. Все асинхронные операции внутри него запускаются одновременно и завершаются всякий раз, когда не учитывается .forEach() цикл. Если вы хотите координировать выполнение нескольких обещаний, вы либо выполняете их последовательно одно за другим, либо используете Promise.all() для получения уведомления, когда все они выполнены. Существуют сотни других вопросов / ответов, объясняющих, как выполнить любой из этих вариантов.

2. Кроме того, поскольку вы используете Bluebird, попробуйте replace .forEach() с помощью функции map Bluebird, у нее также есть удобная опция параллелизма.

3. Вы пытаетесь упорядочить свои URL-адреса один за другим или получить статистику по всем из них параллельно, а затем узнать, когда все они будут готовы?

4. @jfriend00: я пытаюсь упорядочить URL-адреса, как только они будут готовы, и получаю статистику после того, как один будет готов, затем следующий, затем следующий и так далее .. итак, если у нас есть: www.abc.com ,www.xyz.com ,www.aaa.com мне нужна статистика для abc, xyz, а затем aaa.

Ответ №1:

Поскольку вы используете Bluebird, вы можете заменить свой .forEach() цикл на Promise.mapSeries() , и он будет последовательно проходить по вашему массиву, ожидая завершения каждой асинхронной операции, прежде чем выполнять следующую. Результатом будет обещание, разрешенное значение которого представляет собой массив результатов. Вам также следует прекратить объявлять локальные переменные в более широкой области видимости, когда у вас задействованы асинхронные операции. Объявляйте их в ближайшей практической области, которая в данном случае является областью, в которой они используются.

 const Promise = require('bluebird');
const readFile = Promise.promisify(fs.readFile);

recordPerfMetrics: function() {

    var self = this;
    return readFile('urls.txt', 'UTF-8').then(function (urls) {
        var urls = urls.split("n");
        urls.shift();

        return Promise.mapSeries(urls, function(url) {     
            console.log(url);   
            return self.getStats(url).then(function(data) { 
                data = data[0];
                let loadTime = (data.loadEventEnd - data.navigationStart)/1000   ' sec';
                let firstPaint = data.firstPaint;
                let domInteractive = (data.domInteractive - data.navigationStart)/1000   ' sec';

                let perfData = {
                    'URL' : url,
                    'firstPaint' : firstPaint,
                    'loadTime' : loadTime,
                    'domInteractive' : domInteractive
                };
                console.log(perfData);
                return perfData;
            }).catch(function(error) {
                console.log(error);
                throw error;    // keep the promise rejected
            });
        });      

        // console.log(colors.magenta("Starting to record performance metrics for "   url));
        // this.storePerfMetrics();                       
    });    
},

getStats: function(url) {
    return new Promise(function(resolve, reject){
        console.log("Getting data for url: ",url);
        browserPerf(url, function(error, data) {
            console.log("inside browserPerf", url);
            if (!error) {
                resolve(data);
              } else {
                reject(error);
            }
        }, {
            selenium: 'http://localhost:4444/wd/hub',
            browsers: ['chrome']
        });
    });
},
  

Вы бы использовали это следующим образом:

 obj.recordPerfMetrics().then(function(results) {
    // process results array here (array of perfData objects)
}).catch(function(err) {
    // error here
});
  

Краткое изложение изменений:

  1. Возвращать обещание из recordPefMetrics, чтобы вызывающий мог получить данные
  2. Использовать Promise.mapSeries() вместо .forEach() для последовательных асинхронных операций.
  3. Возвращать обещание из Promise.mapSeries() , чтобы оно было связано с предыдущим обещанием.
  4. Переместить объявления переменных в локальную область видимости, чтобы не было изменений в различных асинхронных операциях, воздействующих на общие переменные.
  5. Ошибка повторного ввода .catch() после регистрации, поэтому отклонение распространяется
  6. return perfData таким образом, оно становится разрешенным значением и доступно в массиве результатов.

Комментарии:

1. @jp310 — Не пропустите правки, которые я только что сделал, чтобы исправить еще несколько вещей.