1

I have the question, below code:

The problem is:

  1. How can I send each line from response promiseGetCitiesData to promiseGetInformationDataPerCity.
  2. Can I do it in one async.each functions?

Now, I created multiple Promise functions. One general function, which one start the program - getDataAndCloseDb().

Also I used async.each to call promise function with array parameter - locationArray.

Now, I would like to send each line from json response to next promise function (create get url), and collect the general response.

const MongoClient = require("mongodb").MongoClient;
    const request = require("request");
    const async = require("async");

    var locationsArray = [
      'location1',
      'location2',
      'location3'
    ];

    function promiseConnectToDatabase(urldb) {
      return new Promise(function(resolve, reject) {
        MongoClient.connect(urldb, (err, db) => {
          if (err) {
            console.log("MongoDb connection error.");
            reject(err);
          }
          console.log("Connected to MongoDb.");
          resolve(db);
        });
      });
    }

    function promiseGetCitiesData(location) {
      return new Promise(function(resolve, reject) {
        request({
          url: `https://example.com/${location}`,
          json: true
        }, (error, response, body) => {
          if (error) {
            console.log("Error connection to url.");
            reject();
          }
          console.log("location: " + location);
          console.log({location: location, cities: body.result.cities});
          resolve({location: location, cities: body.result.cities});
        });
      });
    }
    /*
    Example response from promiseGetCitiesData:

    Location: location1
    { location: 'location1',
      cities: 
       [ 'information1',
         'information2',
         'information3',
         'information4'' ] }

    */

    function promiseGetInformationDataPerCity(location, cities) {
      return new Promise(function(resolve, reject) {
        request({
          url: `https://example.com/${location}/${cities}`,
          //f.e https://example.com/location1/information1 etc.
          json: true
        }, (error, response, information) => {
          if (error) {
            console.log("Error connection to url.");
            reject();
          }
          console.log(information);
          resolve(information);
        });
      });
    }

    function promiseSaveDataToDatabase(db, body) {
      return new Promise(function(resolve, reject) {
        db.collection("testlocation").insert(body, function(dbError) {
          if (dbError) {
            reject(dbError);
          }
          resolve()
        });
      });
    }

    function promiseDisconnectDatabase(db) {
      return new Promise(function(resolve, reject) {
        db.close((err) => {
          if (err) {
            console.log("MongoDb disconnect error.");
            reject(err);
          }
          console.log("MongoDb disconnected.");
          resolve();
        });
      });
    }

    function promiseProvideDataFromEach(locationsArray, db) {
      return new Promise(function(resolve, reject) {
        async.each(locationsArray, function(loc, locProcessedCb) {
          promiseGetcitiesData(loc).then(function(resultscities) {
            promiseGetInformationDataPerCity(loc, resultscities).then(function(resultDetails) {
                promiseSaveDataToDatabase(db, resultDetails).then(function() {});
            locProcessedCb();
            });
          });
        }, function(err) {
          if (err) {
            locProcessedCb(err);
            reject(err);
          }
          console.log("All locations have been processed.");
          resolve();
        });
      });
    }

    function getDataAndCloseDb() {
      return new Promise(function(resolve, reject) {
        promiseConnectToDatabase("mongodb://127.0.0.1:27017/testApp").then(function(db) {
          promiseProvideDataFromEach(locationsArray, db).then(function() {
            promiseDisconnectDatabase(db).then(function() {});
          });
        });
      });
    }

    getDataAndCloseDb();
Roamer-1888
  • 19,138
  • 5
  • 33
  • 44
profiler
  • 567
  • 2
  • 15
  • 41
  • I m having trouble understanding what you wanna do. You can check `Promise.all` patterns it will possible do what you want. – Stamos Dec 20 '17 at 08:20
  • I would like to send response from `promiseGetCitiesData {... "location1", "location2"... etc}` to `promiseGetInformationDataPerCity` (like comments in code.). The best solution will be sending it per line: 1. send request for location, get json with information. 2. Send each line from response json to `promiseGetCitiesData`, collect the response, save to database. 3. Again send request for location2 etc... – profiler Dec 20 '17 at 08:38
  • @Antonio-Narkevich you are big expert of node js, java script technologies. Could You please check my problem? – profiler Dec 21 '17 at 06:52
  • I get a bit confused in `promiseGetInformationDataPerCity(location, cities)`, where the url is built as `https://example.com/${location}/${cities}`. `location` is fine providing it is String. but `cities` (plural) is an Array delivered by `promiseGetcitiesData()`. So should `cities` be joined into a String, or should it be `https://example.com/${location}/${city}/` in some sort of loop? – Roamer-1888 Dec 22 '17 at 21:56
  • @Roamer-1888 Thank You for response. Like in my descrpiton. After calling promiseGetCitiesData I got the "Example response from promiseGetCitiesData" (json with "information1", "information2".. etc."). Those "information" I would like to send to: promiseGetInformationDataPerCity and use it to ceate URL adress like: https://example.com/location1/information1, https://example.com/location1/information2... https://example.com/location1/informationN and https://example.com/location2/information1... https://example.com/location2/informationN... https://example.com/locationN/informationN. – profiler Dec 22 '17 at 22:32

2 Answers2

1

I think this is a lot simpler than the code in the question makes it appear. In particular, new Promise(...) can be completely avoided by :

  • using require('async-request') instead of require('request').
  • allowing MongoDb methods to return Promise, as many of them will do if no callback is passed.

Also

  • by using the Promise.all(array.map(...)) pattern the need for require('async') disappears.
  • https://stackoverflow.com/a/28915678/3478010 - provides a great little reusable disposer utility, which is useful here.
  • Remember to return a promise/value from every .then() callback that is itself asynchronous and/or should deliver data.

With some guesswork, I think you want something like this :

const MongoClient = require('mongodb').MongoClient;
const request = require('async-request'); // just like `request()` but returns a promise

var locationsArray = [
    'location1',
    'location2',
    'location3'
];

function promiseGetCitiesData(loc) {
    return request({
        url: `https://example.com/${loc}`,
        json: true
    }).then(body => body.result.cities);
}

function promiseGetInformationDataPerCity(loc, cities) {
    return Promise.all(cities.map(city => {
        return request({
            'url': `https://example.com/${loc}/${city}`,
            'json': true
        }).then(cityInfo => ({ 'name':city, 'info':cityInfo }));
    }));
}

function promiseProvideDataFromEach(locationsArray, db) {
    return Promise.all(locationsArray.map(loc => {
        return promiseGetCitiesData(loc)
        .then(cities => promiseGetInformationDataPerCity(loc, cities)
        .then(citiesWithCityInfo => ({ 'location':loc, 'cities':citiesWithCityInfo }));
    }))
    .then(resultDetails => db.collection('testlocation').insertMany(resultDetails));
}

// disposer utility - credit: https://stackoverflow.com/a/28915678/3478010
function withDb(work) {
    var _db;
    return MongoClient.connect("mongodb://127.0.0.1:27017/testApp")
    .then((db) => {
        _db = db; // keep reference 
        return work(db); // perform work on db
    }).finally(() => {
        if (_db)
            _db.close();
    });
}

withDb(db => promiseProvideDataFromEach(locationsArray, db))
.then(() => {
    // connection released here
});

The guesswork centres mainly around what is to be inserted at db.collection('testlocation').insertMany(resultDetails). The code in the question gives no more than a clue. My attempt seems reasonable but may not be exactly what you want. Be prepared to make some changes in promiseProvideDataFromEach() and promiseGetInformationDataPerCity().

Roamer-1888
  • 19,138
  • 5
  • 33
  • 44
  • Thank You. Still I have problem with all: `then(cityInfo => { 'name':city, 'info':cityInfo });` - error - `unexpected ':" token`. If I removed to, f.e.: `then(cityInfo => body.result);` - the error is: `"arequest = async (url, options)"`. That same in `then(citiesWithCityInfo => { 'location':loc, 'cities':citiesWithCityInfo });`. – profiler Jan 01 '18 at 16:00
  • Ok, missing () in json response. But now i have still the `async-request/src/main.js:141 arequest = async (url, options) => {` error. – profiler Jan 01 '18 at 16:52
  • Yes, my bad, these are cases of "[object literal interpreted as block statement](https://blog.mariusschulz.com/2015/06/09/returning-object-literals-from-arrow-functions-in-javascript)". I forgot brackets! Answer edited. – Roamer-1888 Jan 01 '18 at 16:58
  • `promiseGetCitiesData()` also contained a mistake. Edited. – Roamer-1888 Jan 01 '18 at 17:05
  • Also I did that change (on promiseGetCitiesData). but I have still the error like my last comment: `async-request/src/main.js:141 arequest = async (url, options) => { Unexpected token (.` But there is no information about line. Strange error. Like from async-request package. – profiler Jan 01 '18 at 17:51
  • On Windows (before I run it on Linux), there is another error - `MongoClient.connect(...).then(...).finally is not a function`. – profiler Jan 01 '18 at 18:05
  • And one more question, do You know how to Yous console.log on any response, on that code schema? F.e.: `}).then(projects => body.result.projects);` - I would like print the projects logs... And, in async-request we dont use the reject() on promises? And resolve()? – profiler Jan 01 '18 at 18:45
  • I'm not an expert in Node/Mongoose but can give you some clues, after which you will need to do some research. (a) you may be running a version of Node, that doesn't support async/await as used by `request-async`. (b) `.finally()` is a Bluebird method, and is not supported by Mongoose's built-in mPromise (which is deprecated); [read this](http://mongoosejs.com/docs/promises.html). ... In short, make sure your local environment is as close as possible to your production environment. – Roamer-1888 Jan 01 '18 at 20:36
  • An arrow function of the form `projects => body.result.projects` is equivalent to `projects => { return body.result.projects; }`. For multi-line functions (eg where you want to log data), you must use the latter form. – Roamer-1888 Jan 01 '18 at 20:36
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/162300/discussion-between-profiler-and-roamer-1888). – profiler Jan 01 '18 at 20:55
0

you can do something like this. Its a simpler code but I think you can map it to your current code.

const Promise = require('bluebird')

const cities = ['citya', 'cityb', 'cityc']

function resolveCities() {
  return new Promise(function(resolve, reject) {
    resolve(cities)
  })
}

function logCity(city) {
  console.log('city ', city)
}

return resolveCities() 
.then(function(cities) {
  return Promise.mapSeries(cities, function(city) {
    logCity(city);
  });
})
kperveen
  • 61
  • 1
  • 6
  • did you install the bluebird module? Its working fine on my side. – kperveen Dec 20 '17 at 19:54
  • Yes, the module bluebird was installed. But I cant parse each line to call another promise function. All together - should be working asynchronously. First call promiseGetCitiesData, each line should be send to another promise, one by one, after it the second call promiseGetCitiesData should be send. – profiler Dec 20 '17 at 20:09
  • Okay so trying to get a better hold of your problem you want to send multiple calls to `promiseGetCitiesData` based on some data? Can you please give me an example of the data that you want to send in each request to `promiseGetCitiesData` – kperveen Dec 20 '17 at 20:22
  • if you want to execute it one by one, you will have to use the synchronous flow not async. – kperveen Dec 20 '17 at 20:28
  • Like in my descrpiton. After calling `promiseGetCitiesData` I got the "Example response from promiseGetCitiesData" (json with "information1", "information2".. etc."). Those "information" I would like to send to: `promiseGetInformationDataPerCity` and use it to ceate URL adress like: `https://example.com/location1/information1, https://example.com/location1/information2... https://example.com/location1/informationN` and `https://example.com/location2/information1... https://example.com/location2/informationN... https://example.com/locationN/informationN.` – profiler Dec 20 '17 at 20:28
  • Yes, but I need async to send locationArray to first promise. – profiler Dec 20 '17 at 20:30
  • // if result citites is an array ```async.each(locationsArray, function(loc, locProcessedCb) { promiseGetcitiesData(loc) .then(function(resultscities) { resultscities.forEach(function(city) { return promiseGetInformationDataPerCity(loc, city)``` – kperveen Dec 21 '17 at 07:54
  • try these, I think you need to run two loops if you want url's for each data in the resultCities – kperveen Dec 21 '17 at 07:56