I have the question, below code:
The problem is:
How can I send each line from response promiseGetCitiesData to promiseGetInformationDataPerCity.
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();
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().
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);
});
})
Related
I'm putting together some Node.js code for querying LDAP that uses promises. When I run it, I get Unexpected reserved word concerning the await on line 43. This part:
let connection = await connect(ldapURL).catch((err) => {
console.error('LDAP server error:', err);
reject(err);
});
The entire code is shown below.
I have a promise returned in the connect() function and that's working fine. In fact, if I remove the promise from the listObjects() function, the console.debug(results); line prints exactly what I'm expecting.
So why is the await connect() causing an error in my listObjects() function? My searching has yielded a lot of answers saying, "You need to use async," but I already have my listObjects() declared as async.
Where have I gone wrong?
Full Code:
#!/usr/bin/env node
import ldapjs from 'ldapjs';
const ldapURL = [ 'ldap://127.0.0.1:389' ];
const bindDN = 'uid=search,dc=home';
const bindPassword = 'P#ssw0rd';
function connect(serverURL) {
return new Promise((resolve, reject) => {
const client = ldapjs.createClient({
url: serverURL
});
client.on('connect', () => {
console.debug('Connected to:', ldapURL);
console.debug('Binding as:', bindDN);
client.bind(bindDN, bindPassword, (err) => {
if (err) {
console.debug(err.message);
reject('Bind credentials rejected.');
}
else {
resolve(client);
}
});
});
client.on('error', (err) => {
reject('Unable to connect to ' + serverURL);
});
});
}
/**
* Search LDAP and return objects.
* #baseDN {string} Where to start, like 'ou=People,dc=example,dc=com'
* #filter {string} Optional LDAP query to limit results, like '(objectClass=posixAccount)'
* #returns {promise} ... Eventually.
*/
async function listObjects(baseDN, filter) {
return new Promise((resolve, reject) => {
let connection = await connect(ldapURL).catch((err) => {
console.error('LDAP server error:', err);
reject(err);
});
let opts = {
filter: filter,
scope: 'sub'
};
let results = [];
connection.search(`${baseDN}`, opts, (err, res) => {
res.on('searchEntry', (entry) => {
results.push(entry);
});
res.on('end', () => {
connection.unbind(() => {
console.debug(results);
resolve(results);
});
});
});
});
}
let ldapObjects = await listObjects('dc=home', '(objectClass=posixAccount)');
console.log(ldapObjects);
After helpful suggestions in the comments, the solution was to Move the line return new Promise((resolve, reject) => { down so that it only wraps the connection.search(…) part as suggested by Bergi
Here is the code after that modification:
#!/usr/bin/env node
import ldapjs from 'ldapjs';
const ldapURL = [ 'ldap://127.0.0.1:389' ];
const bindDN = 'uid=search,dc=home';
const bindPassword = 'P#ssw0rd';
function connect(serverURL) {
return new Promise((resolve, reject) => {
const client = ldapjs.createClient({
url: serverURL
});
client.on('connect', () => {
console.debug('Connected to:', ldapURL);
console.debug('Binding as:', bindDN);
client.bind(bindDN, bindPassword, (err) => {
if (err) {
console.debug(err.message);
reject('Bind credentials rejected.');
}
else {
resolve(client);
}
});
});
client.on('error', (err) => {
reject('Unable to connect to ' + serverURL);
});
});
}
/**
* Search LDAP and return objects.
* #baseDN {string} Where to start, like 'ou=People,dc=example,dc=com'
* #filter {string} Optional LDAP query to limit results, like '(objectClass=posixAccount)'
* #returns {promise} ... Eventually.
*/
async function listObjects(baseDN, filter) {
let connection = await connect(ldapURL).catch((err) => {
console.error('LDAP server error:', err);
reject(err);
});
let opts = {
filter: filter,
scope: 'sub'
};
let results = [];
return new Promise((resolve, reject) => {
connection.search(`${baseDN}`, opts, (err, res) => {
res.on('searchEntry', (entry) => {
results.push(entry);
});
res.on('end', () => {
connection.unbind(() => {
resolve(results);
});
});
});
});
}
let ldapObjects = await listObjects('dc=home', '(objectClass=posixAccount)');
console.log(ldapObjects);
I think you have to remove new Promise because async return the data wraps in the Promise. I think that your ldapObjects store a Promise and listObjects return a Promise wrap inside a Promise.
Here's the code
console.log("Before");
const p = getUser(1);
p.then(user => {
console.log(user);
getRepositories(user.github);
}).then(repos => {
console.log(repos);
});
console.log("after");
function getUser(id) {
return new Promise(function(resolve, reject) {
setTimeout(() => {
console.log("Calling a database " + id);
resolve({ id: id, github: "Zain" });
}, 2000);
});
}
function getRepositories(username) {
return new Promise(function(resolve, reject) {
setTimeout(() => {
console.log(`Calling Api for ${username}`);
resolve(["repo1", "repo2", "repo3"]);
}, 2000);
});
}
I'm having trouble with consuming the promise returned by getRepositories() function. Above is my implementation but it doesn't work and returns undefined (instead of the array [repo1, repo2, repo3]).
Here's the output:
I want the array to return after logging "Calling Api for Zain" but the undefined is shown before it, but I don't know why, so I need help regarding this.
You need a return statement in your first .then:
p.then(user => {
console.log(user);
return getRepositories(user.github);
}).then(repos => {
console.log(repos);
});
There’s a special syntax to work with promises in a more comfortable fashion, called “async/await”. It’s surprisingly easy to understand and use.
async function init() {
console.log("Before");
const user = await getUser(1);
console.log(user);
const repos = await getRepositories(user.github);
console.log(repos);
console.log("after");
}
init();
async function getUser(id) {
return new Promise(function(resolve, reject) {
setTimeout(() => {
console.log("Calling a database " + id);
resolve({ id: id, github: "Zain" });
}, 2000);
});
}
async function getRepositories(username) {
return new Promise(function(resolve, reject) {
setTimeout(() => {
console.log(`Calling Api for ${username}`);
resolve(["repo1", "repo2", "repo3"]);
}, 2000);
});
}
For more information: https://javascript.info/async-await
When i call rest API and return response it show undefined but i console.log this response it return
var request = require("request");
function initialize() {
// Setting URL and headers for request
var options = {
url: 'http://postalpincode.in/api/pincode/400605',
json: true
};
// Return new promise
return new Promise(function (resolve, reject) {
// Do async job
request.get(options, function (err, resp, body) {
if (err) {
reject(err);
} else {
resolve(JSON.stringify(body));
}
})
})
}
function main() {
var initializePromise = initialize();
initializePromise.then(function (result) {
return result;
})
}
console.log('', main())
But when i console log this response it show output correct
var request = require("request");
function initialize() {
// Setting URL and headers for request
var options = {
url: 'http://postalpincode.in/api/pincode/400605',
json: true
};
// Return new promise
return new Promise(function (resolve, reject) {
// Do async job
request.get(options, function (err, resp, body) {
if (err) {
reject(err);
} else {
resolve(JSON.stringify(body));
}
})
})
}
function main() {
var initializePromise = initialize();
initializePromise.then(function (result) {
console.log('', result)
})
}
console.log('', main())
I want When i call rest API and return response it show correct output
The return inside the thenscope is not returning for the function main, but only for the thenscope of the promise. You need to return the promise like so:
function main() {
var initializePromise = initialize();
return initializePromise.then(function (result) {
return result;
})
}
main().then((result) => console.log('',result));
you can't make a sync function call an async method and expect to have get its result.
use async/await
async function main() {
var initializePromise = await initialize();
console.log(initializePromise)
}
My question is, why are you wrapping in a new Promise something that's already from a return type of Promise?
You could just do:
request.get(endpoint, options).then((response) => console.log(response)).catch((error) => console.log(error));
Let me know what's the output in that case.
The then resolution of initializePromise method resolves at a later stage when a response is fetched from REST HTTP call i.e. it does not get returned when you call main() method due to the fact it is async. To handle such code, you should either use a callback
function main(completionHandler) {
var initializePromise = initialize();
initializePromise.then(function (result) {
completionHandler(result);
})
}
main((result) => { console.log(result)})
or a promise
function main() {
// Return new promise
return new Promise(resolve => {
var initializePromise = initialize();
initializePromise.then(function (result) {
resolve(result);
})
}
}
main().then(result => console.log(result));
return new Promise(function (resolve, reject) {
// Do async job
request.get(options, function (err, resp, body) {
if (err) {
reject(err);
} else {
try {
resolve(JSON.stringify(body));
} catch(e) {
reject(e);
}
}
})
})
in main function:
function main() {
initialize().then((result) => {
console.log(result);
return result;
}).catch((err) => {
console.log(err);
return err;
})
}
I'm trying to merge the data from two JSON files (customer list) and then, create as many task as customers there are to be executed later via async.js
In order to have all the tasks array full, I'm controlling the end of the forEach with a promise:
var parsePromise = new Promise(function (resolve, reject) {
mongoDB.MongoClient.connect(dbURL, (error, db) => {
originalData.forEach(function (element, index) {
var restoredCustomer = Object.assign(element, backupData[index]);
tasksArray.push((function (db, element) {
return function () {
db.collection('customers').insert(element, function (error) {
if (error) {
console.error("Error: ", error);
}
})
}
})(db, restoredCustomer));
forEachCounter--;
if (forEachCounter === 0) {
resolve(tasksArray);
}
});
});
});
Then, when the promise ends, I execute the async.js parallel method:
parsePromise.then(function (tasksArray) {
async.parallel(tasksArray, (err, results) => {
console.log("Operation completed successfully: ", results);
})
})
The weird thing is that the code is working fine, and the inserts are being done on MongoDB, however, the console.log("Operation completed successfully: ", results); placed in the parallel callback is never shown.
Try this
var parsePromise = new Promise(function (resolve, reject) {
mongoDB.MongoClient.connect(dbURL, (error, db) => {
originalData.forEach(function (element, index) {
var restoredCustomer = Object.assign(element, backupData[index]);
tasksArray.push(function(cb){
db.collection('customers').insert(restoredCustomer, function (error) {
if (error) return cb(err);
cb();
})
});
forEachCounter--;
if (forEachCounter === 0) {
resolve(tasksArray);
}
});
});
});
According to async js documentation
https://caolan.github.io/async/global.html
tasks arguments in parallel function in async.js must be AsyncFunction.
AsyncFunction requires to have the final parameter which is a callback. The structure of the callback follows the standard of function(err, args).
Also you can try simplifying your code to:
new Promise((resolve, reject) => {
mongoDB.MongoClient.connect(dbURL, (error, db) => {
if (error) return reject(error);
resolve(db);
});
}).then(db => {
var tasksArray = [];
for (let i = 0; i != originalData.length; ++i){
var restoredCustomer = Object.assign(originalData[i], backupData[index]);
tasksArray.push(function(cb){
db.collection('customers').insert(restoredCustomer, function (error) {
if (error) return cb(err);
cb();
})
});
}
return new Promise((resolve, reject) => {
async.parallel(tasksArray, (err, results) => {
if (err) return reject(err);
resolve(results)
})
})
})
Normally if I was going to run multiple mongoose queries I would use the built in promise to chain them all together. In this case, the user chooses which schemas to search. This could be one of them or both. The following example uses the post data to define which schemas to search and if one is false, it should continue through the promise chain. Right now the final promise is being called before the queries.
Example in my express controller:
app.post('/custom-search', function (req, res) {
var single = false
var multi = false
if(req.body.single){
var single = true
}
if(req.body.multi){
var multi = true
}
var promise = new Promise(function (resolve, reject) {
if(multi){
multiSchema.find({}, function (err, result) {
if(!err){
console.log(result);
resolve()
}
})
}else{
resolve()
}
}).then(function (value) {
if(single){
singleSchema.find({}, function (err, result) {
if(!err){
console.log(result);
resolve()
}
})
}else{
resolve()
}
}).then(function (value) {
console.log("done");
})
})
});
output:
>done
>[singleResults]
>[multiResults]
done should be printing last so that is the first problem.
Like we discussed, few things had to be clean up. First by actually using and returning the promise for it work properly, Second, creating a mini-promise within your first .then() to resolve and reject your single conditional statement. And third, handling/catching promises.
I wrote a pseudo version of your code to illustrate my point of view, hopefully it may be of a good use.
app.get('/custom-search', function (req, res) {
// Manipulating values to test responses
var single = false;
var multi = true;
var promise = new Promise(function (resolve, reject) {
if (multi) {
setTimeout(function () {
resolve('MULTI RESOLVED!');
}, 3000);
} else {
reject('MULTI REJECTED!');
}
})
.then(function (value) {
new Promise(function (resolve, reject) {
if (single) {
setTimeout(function () {
resolve('SINGLE RESOLVED!');
}, 3000);
} else {
reject('SINGLE REJECTED!');
}
})
.catch(function (error) {
console.log('SINGLE ERROR!', error);
})
.then(function (value) {
console.log('SINGLE DONE', value);
});
})
.catch(function (error) {
console.log('MULTI ERROR!', error);
});
return promise;
});