-3

I have this code, what I want is to wait for the Promise.all after the comment EXECUTION to be finished and then proceed to do other task. Notice that I'm using PromiseJS, not BlueBird. I've searched questions about await/async but none of them work for me. Sorry if you feel the code is long as I want to expose it all. Maybe without doing this some of you might say "maybe there is a bug somewhere ".

// ================ load the lib ================ 
const curl = require("curl");
const jsdom = require('jsdom');
const cheerio = require('cheerio');
const Promise = require("promise");


// ================ declare global constants ================ 
const domain = "https://www.bankmega.com";
var url = domain + "/ajax.promolainnya.php";
const categories = [1, 2, 3, 4, 5, 6]; // hard-code subcat but later can be changed
                                       // simply by loading the main page, then get all the subcat
                                       // and convert to an appropriate integer array representing the
                                       // categories
var visited = new Set(); // store visited links (not to scrap an item twice)


// ================  declare methods ================ 
function getItemLinksOfCat(url, subcat) {
    const subCatURL = url + "?product=&subcat=" + subcat;
    curl.get(subCatURL, null, (err, resp, body) => {
        const {JSDOM} = jsdom;
        const dom = new JSDOM(body);
        const $ = (require("jquery"))(dom.window);
        var tds = $("table[class=tablepaging] tr").children();
        var maxPage = getMaxPage(tds, $);
        var itemLinks = getItemLinks(maxPage, $, subcat);

        // itemLinks.forEach(itemLink => {
        //  if (!visited.has(itemLink)) {
        //      visited.add(itemLink);
        //      scrapItem(itemLink);
        //  }
        // });

        Promise.all(itemLinks.map(function(itemLink) {
            if (!visited.has(itemLink)) {
                visited.add(itemLink);
                scrapItem(itemLink);
            }
        }));
    });

}

function getItemLinks(maxPage, $, subcat) {
    var itemLinks = [];
    var product = "";
    for (var i = 1; i <= maxPage; ++i) {
        var page = i;
        $("#contentpromolain2").load("ajax.promolainnya.php?product="+product+"&subcat="+subcat+"&page="+page);
        var lis = $("ul#promolain").children();
        for (var j = 0; j < lis.length; ++j) {
            var itemLink = $(lis[j]).find("a").attr("href");
            itemLinks.push(itemLink);
        }
    }

    return itemLinks;
}

function getMaxPage(tds, $) {
    var maxPage = -1;

    for(var i = 0; i < tds.length; ++i ){
        var td = $(tds[i]);
        var page = parseInt(td.text());
        if (page != NaN && page > maxPage) {
            maxPage = page;
        }
    }

    return maxPage;
}

/*
Using wrapper method might be useful in the future
As we can redirect a call to an appropriate method
that can handle a specific type of item
 */
function scrapItem(itemLink) {
    if(itemLink.includes("promo_detail")) {
        scrapPromoDetail(itemLink);
    }
}

/*
Actual method to scrap promo item
We can have other methods to scrap other types of item
*/
function scrapPromoDetail(itemLink) {
    itemLink = domain + "/" + itemLink;
    curl.get(itemLink, null, (err, resp, body) => {
        if (resp != undefined && resp.statusCode == 200) {
            var s = parseItemHTMLToString(body, itemLink);
            console.log(s);
            console.log("========");
        }
    });

}

/*
Helper function to parse item's html to string
Return a string contains item's property-value pairs
*/
function parseItemHTMLToString(html, itemLink) {
    const $ = cheerio.load(html);
    var promoSection = $("div#contentpromolain2");
    var promoProperties = promoSection.find("div");
    var dict = {};

    for (var i = 0; i < promoProperties.length; ++i) {
        var div = $(promoProperties[i]);
        var klass = div.attr("class");
        var text = div.text().trim();
        if (klass !== undefined) {
            if (klass === "titleinside") { // title
                dict[klass] = text;
            } else {
                if (klass === "periode" || klass === "area" ) { // other props
                    var token = text.split(":");
                    text = token[1];
                    if (klass === "periode") {
                        token = text.split("-");
                        for(var j = 0; j < token.length; ++j) {
                            token[j] = token[j].trim();
                        }
                        dict[klass] = token.join(" - ");
                    } else { // area
                        dict[klass] = text;
                    }
                } else if (klass === "keteranganinside") { // promo image
                    dict[klass] = domain + div.find("img").attr("src").trim();
                } else { // other props
                    dict[klass] = text;
                }
            }
        }
    }

    return dict;
}

// ================ EXECUTION ================ 
Promise.all(categories.map(function(subcat) {
    getItemLinksOfCat(url, subcat)

}));

// do other tasks after Promise.all

EDIT 1 I've tried this:

// ================ EXECUTION ================ 
async function ttt() {
    await Promise.all(categories.map(function(subcat) {
        getItemLinksOfCat(url, subcat)

    }));
    // do other tasks after Promise.allc
}

ttt().then( result => {
console.log("Finish");
});

but it didn't work.

Here's the portion of the output:

Finish
{ titleinside: 'Mega Cellular - Free Tempered Glass',
  area: ' Pontianak',
  periode: '20 Juli 2018 - 18 Oktober 2018',
  keteranganinside:
   'https://www.bankmega.com/files/images/00-landing-page-MEGACELL.jpg' }
========

EDIT 2 Hi HoldOffHunder, you mean this?

// ================ EXECUTION ================ 
async function test() {
    await Promise.all(categories.map(function(subcat) {
        getItemLinksOfCat(url, subcat)
    }));
    // do other tasks after Promise.allc

    console.log("???");
}

test();

It also printed out "???" before running.

loctv
  • 3
  • 1
  • Possible duplicate of [How do I return the response from an asynchronous call?](https://stackoverflow.com/questions/14220321/how-do-i-return-the-response-from-an-asynchronous-call) – Liam Sep 21 '18 at 15:46
  • 3
    [It is really hard to answer a question about a bug in code when the question includes a huge block of code. When this happens, it takes much longer and is much more difficult for other users to search through the code to find the pieces that are relevant to the problem.](http://idownvotedbecau.se/toomuchcode/). Please try an minimise your examples. i.e. create a [**Minimal**, Complete, and Verifiable example](https://stackoverflow.com/help/mcve) of your problem – Liam Sep 21 '18 at 15:47
  • All of you people just trying to attack the comment, not really try to answer it. I did add .then but it doesn't work. Also I've searched this question before posting it. Really don't want to post anything at all because there are many predators out there and they always ready to give you a ton of "-1". – loctv Sep 21 '18 at 15:53
  • 1
    To make things simpler, dont use `Array.map` function. Instead, pass an array of functions that ***return promises*** to `Promise.all` and use `.then()` like HoldOffHunger stated. – Isaac Vidrine Sep 21 '18 at 15:56
  • Decided to broaden my comment out a bit into an answer, may be something similar here, but, this fits my expertise domain. – HoldOffHunger Sep 21 '18 at 15:58
  • Thanks all for the reply, and there you have it, the predators will come and add tons and tons of "-1" for sure. – loctv Sep 21 '18 at 16:00
  • ttt() does not return a promise array. you should try something simpler. Just have await Promise.all(), and then console.log ("Promise done."); – HoldOffHunger Sep 21 '18 at 16:12

2 Answers2

1

Promise.all(...) must take an array of Promise-type variables. And it should be followed by .then(() => { //success code results}, but I am not seeing that.

Check it out: Mozilla Developer Network: Promise.all()

A promise would look like...

var promise3 = new Promise(function(resolve, reject) {...});

And you would use Promise.all() like...

var promisearray = [
    //somepromiseshere
];

Promise.all(
        promisearray
).then(() => {
        console.log("Promise all finished.");
}, () => {
        console.log("Promise all failed.");
});

Or, another method using await() --

Since Promise.all() is asynchronous, you can use await, too, instead, like so...

await Promise.all(promisearray);

Check it out: Mozilla Developer Network: Async Function

The first method is preferable, as it contains catch blocks, but there's no reason you cannot combine these in various ways.

HoldOffHunger
  • 18,769
  • 10
  • 104
  • 133
0

I understand you're new to stack overflow, and probably working with javascript and promises. Here is an example of using Promise and Promise.all()

var people = ["James", "John", "Aaron", "Lane", "Josh", "Isaac"];
let cars = ["Subaru", "Ford", "Chevrolet", "Mercedes", "Toyota"];


let getPeople = () => {
  let promise = new Promise((resolve, reject) => {
    if (people)
     resolve(people);
    else
      reject("People does not exist");
  })
  
  return promise;
}

let getCars = () => {
  let promise = new Promise((resolve, reject) => {
    if (cars)
     resolve(cars);
    else
      reject("cars does not exist");
  });
  
  return promise;
}

let promiseArr = [getPeople(), getCars()]

Promise.all(promiseArr)
  .then((resp) => {
    console.log(resp[0])
    console.log(resp[1])
  })
  .catch((err) => {
    console.log(err)
  })
Isaac Vidrine
  • 1,568
  • 1
  • 8
  • 20
  • Thanks, is `resolve` the method we want to call for each item in `people` and `cars` or for all `people` and `cars`? – loctv Sep 21 '18 at 16:28
  • We call `resolve` and pass in whatever data we want to be returned by `.then` – Isaac Vidrine Sep 21 '18 at 16:35
  • Can we modify `resolve`? As in my example, for each category in the home page, and for each page in the pagination section of each category, I try to get all the item links. There are two `Promise.all`, one for the categories, and one for the links in each page. So there are two `Promise.all` and they are nested. – loctv Sep 21 '18 at 16:40
  • 1
    Let's say in the `getCars` function, before calling `resolve`, can I call `cars.map` to process all cars and the the result, then call `resolve(result)`? – loctv Sep 21 '18 at 16:53
  • @loctv : This explanation looks fine to me. I'm not sure what problem you're having here. You may want to consider testing and experimenting a bit more, or maybe ask a new question? Just a thought. SO tries to have quick solutions to quick problems. – HoldOffHunger Sep 21 '18 at 17:00
  • loctv, yup! you can pass whatver you want into resolve – Isaac Vidrine Sep 21 '18 at 17:06