Функция обратного вызова Node JS

Недавно я погрузился в nodejs (и использую nightmare.js) для анализа веб-сайта, и у меня возникли проблемы с функциями обратного вызова и отображением возвращенных результатов. Я пытаюсь вызвать отдельную функцию в другой функции, но не могу вернуть результаты. Все они возвращают неопределенное. Любая помощь в этом очень ценится.

 function getDetails(loadURL, callback){
    nightmare.goto(loadURL)
        .wait(2000)
        .evaluate(function(){
            var gigs = [];
            $('.hidden-xs .used-vehicle').each(function(){
                item = {}
                item["year"] = $(this).attr('data-year')
                item["make"] = $(this).attr('data-make')
                item["model"] = $(this).attr('data-model')
                item["body"] = $(this).attr('data-body')
                item["color"] = $(this).attr('data-ext-color')
                item["trim"] = $(this).attr('data-trim')
                item["mileage"] = $(this).attr('data-mileage')
                item["transmission"] = $(this).attr('data-transmission')
                item["vin"] = $(this).find(".vehicle-overview").attr('id')
                item["title"] = $(this).find(".vehicle-overview h2 a").text()
                item["link"] = $(this).find(".vehicle-overview h2 a").attr('href')
                item["price"] = $(this).find(".vehicle-content .price").text()
                gigs.push(item)
            })
            return gigs
        })
        .end()
        .then(function(result){
            var returnString = '';
            for(gig in result){
                returnString = returnString + result[gig].title + " " + result[gig].link + " " + result[gig].year + " " + result[gig].make + " " + result[gig].model + " " + result[gig].body + " " + result[gig].color + " " + result[gig].trim + " " + result[gig].transmission + " " + result[gig].vin + " " + result[gig].price + "\n"
            }
            callback(returnString)
        })  
}

    // We will need to get the total amount of pages that we need to parse
    function getInventory(sURL, callback){
        nightmare.goto(sURL)
            .wait(2000)
            .evaluate(function(){
                totals = [];
                items = {}
                totalCars = $('.total-found .count').text()
                carsOnPage = $('.hidden-xs .used-vehicle').size()
                items['carTotal'] = totalCars
                items['onPage'] = carsOnPage
                var pageCalc = (totalCars / carsOnPage)
                items['tPages'] = Math.ceil(pageCalc)
                totals.push(items)
                return totals
            })
            .end()
            .then(function(result){
                var totalCars = '';
                var totalPages = '';
                for (item in result){
                    totalPages = result[item].tPages
                    totalCars = result[item].carTotal               
                }
                counter = 0;
                newURL = '';
                returnDetails = '';
                for (i =0; i < totalPages; i++){
                    if (i == 0){
                        newURL = sURL;
                    } else {
                        counter = i + 1;
                        newURL = sURL + "#action=im_ajax_call&perform=get_results&_post_id=5&page=" + counter + "&show_all_filters=false";
                    }
                    //console.log(newURL)
                    getINV = getDetails(newURL, function(returnString){
                        callback(returnString)
                    })
                    returnDetails = returnDetails + getINV
                }
                callback(returnDetails)
            })
    }

    getInventory(startURL, function(result){
        console.log(result)
    })

4 ответа

Решение

Изучение обещаний и обратных вызовов с асинхронными функциями - довольно сложная задача. Я смог заставить это работать со следующим. Спасибо всем за вашу помощь и направление. Каждый ответ отправлял меня в разные кроличьи норы, чтобы в конечном итоге найти решение.

    function getInventory(sURL){    
    nightmare.goto(sURL)
        .wait(2000)
        .evaluate(function(){
            totals = [];
            items = {}
            totalCars = $('.total-found .count').text()
            carsOnPage = $('.hidden-xs .used-vehicle').size()
            items['carTotal'] = totalCars
            items['onPage'] = carsOnPage
            var pageCalc = (totalCars / carsOnPage)
            items['tPages'] = Math.ceil(pageCalc)
            totals.push(items)
            return totals
        })
        .then(result => {
            var totalCars = '';
            var totalPages = '';
            for (item in result){
                totalPages = result[item].tPages
                totalCars = result[item].carTotal               
            }
            counter = 0;
            let links = [];
            let returnLinks = '';
            newURL = '';            
            for (i = 0; i < totalPages; i++){
                if (i == 0){
                    newURL = sURL;
                } else {
                    counter = i + 1;
                    newURL = sURL + "#action=im_ajax_call&perform=get_results&_post_id=5&page=" + counter + "&show_all_filters=false";
                }
                links.push(newURL);
            }
            return links;
        })
        .then(results => {
            var arrayLinks = results;
            arrayLinks.reduce(function(accumulator, url){
                return accumulator.then(function(newResults){
                    return nightmare.goto(url)
                        .wait(5000)
                        .evaluate(() => {
                            const gigs = [];
                            $(".hidden-xs .used-vehicle").each(function() {
                                item = {};
                                item["year"] = $(this).attr("data-year");
                                item["make"] = $(this).attr("data-make");
                                item["model"] = $(this).attr("data-model");
                                item["body"] = $(this).attr("data-body");
                                item["color"] = $(this).attr("data-ext-color");
                                item["trim"] = $(this).attr("data-trim");
                                item["mileage"] = $(this).attr("data-mileage");
                                item["transmission"] = $(this).attr("data-transmission");
                                item["vin"] = $(this).find(".vehicle-overview").attr("id");
                                item["title"] = $(this).find(".vehicle-overview h2 a").text();
                                item["link"] = $(this).find(".vehicle-overview h2 a").attr("href");
                                item["price"] = $(this).find(".vehicle-content .price").text();
                                gigs.push(item);
                            });
                            return gigs;
                        })
                        .then(detail => {                       
                            for (gig in detail) {
                                try {
                                    var carVin = detail[gig].vin;
                                    var carTitle = detail[gig].title;
                                    var carDescrip = detail[gig].year + " " + detail[gig].make + " " + detail[gig].model;
                                    var carURL = detail[gig].link;
                                    var carMake = detail[gig].make;
                                    var carModel = detail[gig].model;
                                    var carYear = detail[gig].year;
                                    var carMileageFull = detail[gig].mileage;
                                    var carMileage = carMileageFull.replace(',', '');
                                    var carTransmission = detail[gig].transmission;
                                    var carBody = detail[gig].body;
                                    var carPriceFull = detail[gig].price;
                                    var carPriceFull = carPriceFull.replace('$', '');               
                                    var carPriceFull = carPriceFull.replace('*', '');               
                                    var carPriceFull = carPriceFull.replace(',', '');               
                                    var carPrice = carPriceFull.trim();
                                    var dealerAddress = "{addr1: '"+ addressFull.addr1 +"', city: '"+ addressFull.city +"', region: '"+ addressFull.region +"', postal_code: '"+ addressFull.postal_code +"', country: '"+ addressFull.country +"'}";
                                    var dealerLat = latLongFull.latitude;
                                    var dealerLong = latLongFull.longitude;
                                    var carColor = detail[gig].color;

                                    arrSetup = [carVin, carTitle, carDescrip, carURL, carMake, carModel, carYear, carMileage, 'MI', '', '', 'AUTOMATIC', 'GASOLINE', 'OTHER', 'Other', carVin, 'OTHER', carPrice + " USD", dealerAddress, carColor, carPrice + " USD", 'AVAILABLE', 'USED', dealerLat, dealerLong];
                                    newResults.push(arrSetup);
                                }
                                catch(error){
                                    returnString += error;
                                }
                            }
                            return newResults;

                        })
                        .catch(error => {
                            throw new Error(error);
                        });

                });             
            }, Promise.resolve([]))
                .then(function(finalCall){

                    /*
                        We need to get the 3rd image on every vdp in the array. We will need to create a loop, go to the page, get the image and properly insert it into the proper array index
                    */

                    finalCall.reduce(function(accumulator, resultArray){
                        return accumulator.then(function(finalResults){
                            var vdp = resultArray[3];
                            return nightmare.goto(vdp)
                                .wait(500)
                                .evaluate(() => {
                                    var thirdIMG = $('.gallery-thumbs .owl-item:nth-of-type(3) img').attr('src');
                                    return thirdIMG;
                                })
                                .then(imgResult => {
                                    // 9
                                    resultArray.splice(9, 1, imgResult);
                                    console.log(resultArray);
                                    finalResults.push(resultArray);
                                    return finalResults;
                                })
                                .catch(error => {
                                    throw new Error(error);
                                });

                        });
                    }, Promise.resolve([]))
                        .then(finalInsert => {
                            const csvWriter = createCsvWriter({
                            header: ["vehicle_id", "title", "description", "url", "make", "model", "year", "mileage.value", "mileage.unit", "image[0].url", "image[0].tag[0]", "transmission", "fuel_type", "body_style", "drivetrain", "vin", "condition", "price", "address", "exterior_color", "sale_price", "availability", "state_of_vehicle", "latitude", "longitude"],
                                path: 'test.csv'
                            });

                            var records = finalInsert;
                            console.log(records)
                            csvWriter.writeRecords(records)
                            .then(() => {
                                nightmare.end();
                                console.log('...Done');
                            });
                        })
                });
        })      
        .catch(function(error){
            return error;
        })
}

    getInventory(startURL, function(response){
        try {
            console.log("This is the response" + response);
        }
        catch(error){
            console.log(error)
        }
    });

Я не буду говорить вам, что вы не должны смешивать обратные вызовы с такими обещаниями. Но давайте посмотрим на проблему сейчас.

Случай 1

Как насчет того, чтобы проверить и ошибки? Может быть, ваш скрипт выдает ошибки. Я вижу, вы звоните на .then но ничего на .catch, Может быть, тогда никогда не получит никаких данных.

Дело 2

Давайте проверим ваши функции. Ты звонишь .end каждый раз. Ты тоже каждый раз создаешь новый экземпляр Nightmare?

На getInventory функция, вы не должны вызывать .end, На getDetails функция, вы не должны вызывать .end, Это завершает кошмарные случаи, и вы теряете свои данные.

Вызов nightmare.end() после того, как вы сделали со всеми своими функциями и работами. Чтобы сделать это должным образом, вам нужно будет больше узнать о проверке случая 3 ниже.

Дело 3

Узнайте, как работают обещания. В строке ниже вы никогда не ждете завершения функции.

getINV = getDetails(newURL, function(returnString){
 callback(returnString)
})

Вам следует дождаться обещаний, чтобы закончить. Кроме того, убедитесь, что кошмар не пытается просмотреть две ссылки одновременно.

Так что давай, узнай об обещаниях и асинхронных вещах.

Как бы я решил ваш код?

Я бы использовал Promise.all,.map и кучу других новинок. Вот некоторый пример кода, созданный для вас, не копируйте вставку или запускайте код напрямую, попытайтесь понять, почему он отличается от вашего кода и что может быть в результате.

const pLimit = require("promise-limit")(2);

function getDetails(loadURL) {
  return nightmare
    .goto(loadURL)
    .wait(2000)
    .evaluate(() => {
      const gigs = [];
      $(".hidden-xs .used-vehicle").each(function() {
        item = {};
        item["year"] = $(this).attr("data-year");
        item["make"] = $(this).attr("data-make");
        item["model"] = $(this).attr("data-model");
        item["body"] = $(this).attr("data-body");
        item["color"] = $(this).attr("data-ext-color");
        item["trim"] = $(this).attr("data-trim");
        item["mileage"] = $(this).attr("data-mileage");
        item["transmission"] = $(this).attr("data-transmission");
        item["vin"] = $(this)
          .find(".vehicle-overview")
          .attr("id");
        item["title"] = $(this)
          .find(".vehicle-overview h2 a")
          .text();
        item["link"] = $(this)
          .find(".vehicle-overview h2 a")
          .attr("href");
        item["price"] = $(this)
          .find(".vehicle-content .price")
          .text();
        gigs.push(item);
      });
      return gigs;
    })
    .then(result => {
      let returnString = "";
      for (gig in result) {
        returnString =
          `${returnString +
result[gig].title} ${result[gig].link} ${result[gig].year} ${result[gig].make} ${result[gig].model} ${result[gig].body} ${result[gig].color} ${result[gig].trim} ${result[gig].transmission} ${result[gig].vin} ${result[gig].price}\n`;
      }
      return returnString;
    })
    .catch(error => {
      throw new Error(error);
    });
}

// We will need to get the total amount of pages that we need to parse
function getInventory(sURL) {
  return nightmare
    .goto(sURL)
    .wait(2000)
    .evaluate(() => {
      totals = [];
      items = {};
      totalCars = $(".total-found .count").text();
      carsOnPage = $(".hidden-xs .used-vehicle").size();
      items["carTotal"] = totalCars;
      items["onPage"] = carsOnPage;
      const pageCalc = totalCars / carsOnPage;
      items["tPages"] = Math.ceil(pageCalc);
      totals.push(items);
      return totals;
    })
    .then(result => {
      let totalCars = "";
      let totalPages = "";
      for (item in result) {
        totalPages = result[item].tPages;
        totalCars = result[item].carTotal;
      }
      counter = 0;
      newURL = "";
      urls = [];
      returnDetails = [];
      for (i = 0; i < totalPages; i++) {
        if (i == 0) {
          newURL = sURL;
        } else {
          counter = i + 1;
          newURL =
            `${sURL}#action=im_ajax_call&perform=get_results&_post_id=5&page=${counter}&show_all_filters=false`;
        }
        // push to the url array
        // use .map for cleaner code
        urls.push(newURL);
      }
      // return a new promise with concurrency limit
      return Promise.all(
        urls.map(url => {
          return limit(() => getDetails(newURL));
        })
      );
    })
    .catch(error => {
      throw new Error(error);
    });
}

getInventory(startURL)
  .then(result => {
    console.log(result);
  })
  .catch(error => {
    console.err(error);
  });

Ресурсы:

Попробуйте использовать обратный вызов вместо возврата

function getDetails(loadURL, callback){
    nightmare.goto(loadURL)
        .wait(2000)
        .evaluate(function(callback){
            var gigs = [];
            $('.hidden-xs .used-vehicle').each(function(){
                item = {}
                item["year"] = $(this).attr('data-year')
                item["make"] = $(this).attr('data-make')
                item["model"] = $(this).attr('data-model')
                item["body"] = $(this).attr('data-body')
                item["color"] = $(this).attr('data-ext-color')
                item["trim"] = $(this).attr('data-trim')
                item["mileage"] = $(this).attr('data-mileage')
                item["transmission"] = $(this).attr('data-transmission')
                item["vin"] = $(this).find(".vehicle-overview").attr('id')
                item["title"] = $(this).find(".vehicle-overview h2 a").text()
                item["link"] = $(this).find(".vehicle-overview h2 a").attr('href')
                item["price"] = $(this).find(".vehicle-content .price").text()
                gigs.push(item)
            })
            callback(gigs)
        })
        .end()
        .then(function(result){
            var returnString = '';
            for(gig in result){
                returnString = returnString + result[gig].title + " " + result[gig].link + " " + result[gig].year + " " + result[gig].make + " " + result[gig].model + " " + result[gig].body + " " + result[gig].color + " " + result[gig].trim + " " + result[gig].transmission + " " + result[gig].vin + " " + result[gig].price + "\n"
            }
            callback(returnString)
        })  
}

    // We will need to get the total amount of pages that we need to parse
    function getInventory(sURL, callback){
        nightmare.goto(sURL)
            .wait(2000)
            .evaluate(function(){
                totals = [];
                items = {}
                totalCars = $('.total-found .count').text()
                carsOnPage = $('.hidden-xs .used-vehicle').size()
                items['carTotal'] = totalCars
                items['onPage'] = carsOnPage
                var pageCalc = (totalCars / carsOnPage)
                items['tPages'] = Math.ceil(pageCalc)
                totals.push(items)
                return totals
            })
            .end()
            .then(function(result){
                var totalCars = '';
                var totalPages = '';
                for (item in result){
                    totalPages = result[item].tPages
                    totalCars = result[item].carTotal               
                }
                counter = 0;
                newURL = '';
                returnDetails = '';
                for (i =0; i < totalPages; i++){
                    if (i == 0){
                        newURL = sURL;
                    } else {
                        counter = i + 1;
                        newURL = sURL + "#action=im_ajax_call&perform=get_results&_post_id=5&page=" + counter + "&show_all_filters=false";
                    }
                    //console.log(newURL)
                    getINV = getDetails(newURL, function(returnString){
                        callback(returnString)
                    })
                    returnDetails = returnDetails + getINV
                }
                callback(returnDetails)
            })
    }

    getInventory(startURL, function(result){
        console.log(result)
    })

Может быть, сработает следующее: измените циклы, чтобы уменьшить и отобразить, вынули jQuery и внесли небольшие изменения.

Самые важные из них:

  1. Получение атрибута из html-элемента возвращает строку, вы должны преобразовать ее в число.
  2. Избавился от обратных вызовов и получил функции, возвращающие обещания.

Вот код:

const getDetails = loadURL =>
nightmare.goto(loadURL)//return promise here
.wait(2000)
.evaluate(
  ()=>
    Array.from(document.querySelectorAll('.hidden-xs .used-vehicle'))
    .reduce(
      (all,item)=>
        all.concat(
          [
            element.getAttribute('data-year'),
            element.getAttribute('data-make'),
            element.getAttribute('data-model'),
            element.getAttribute('data-body'),
            element.getAttribute('data-ext-color'),
            element.getAttribute('data-trim'),
            element.getAttribute('data-mileage'),
            element.getAttribute('data-transmission'),
            element.querySelector(".vehicle-overview").getAttribute('id'),
            element.querySelector(".vehicle-overview h2 a").innerText,
            element.querySelector(".vehicle-overview h2 a").getAttribute('href'),
            element.querySelector(".vehicle-content .price").innerText
          ].join(" ")
        ),
      []//the all array
    )
);

// We will need to get the total amount of pages that we need to parse
const getInventory = sURL =>
nightmare.goto(sURL)
  .wait(2000)
  .evaluate(
    ()=> {
      //there is only one item here, not sure why you push it into totals
      //  and call it items
      const item = {}
      //getAttribute returns a string, parse it to number
      totalCars = parseInt(document.querySelector('.total-found .count').innerText,10);
      carsOnPage = document.querySelectorAll('.hidden-xs .used-vehicle').length;
      item['carTotal'] = totalCars
      item['onPage'] = carsOnPage
      var pageCalc = (totalCars / carsOnPage)
      item['tPages'] = Math.ceil(pageCalc)
      return item;  
    }
  )
  .then(
    totalItem =>{
      var totalCars = '';
      var totalPages = '';
      totalPages = totalItem.tPages
      totalCars = totalItem.carTotal
      newURL = '';
      returnDetails = '';
      return Array.from(new Array(totalPages),(_,index)=>index+1)
      .reduce(
        (p,counter)=>
          p.then(
            results=>{
              if (counter === 1) {
                newURL = sURL;
              } else {
                newURL = sURL + "#action=im_ajax_call&perform=get_results&_post_id=5&page=" + counter + "&show_all_filters=false";
              }
              return getDetails(newURL)
              .then(
                result=>results.concat(result)
              );
            }
          ),
          Promise.resolve([])
      );
    }
  );

getInventory(startURL)
.then(
result=>
  console.log(result)
).catch(
err=>
  console.warn("Something went wrong:",err)
);
Другие вопросы по тегам