I am trying to use promise in my loop but the loop doesn't wait but prints an empty array value which is declared above. Here is my code:
node:
let total_json = [];
await Promise.all(results.map(async (element) => {
if (element.start_date <= d && element.end_date >= d) {
let query = "select * from tb_voucher_category where id = " + "'" + element.cateID + "'";
body.query = query;
COMMON(body, (err, results) => {
if (err) { fatal_error.data = err; return res.json(fatal_error); }
if (results) {
if (results.length > 0) {
cate = results[0].cateName;
} else {
cate = "";
}
let json = {
id: element.id,
catename: cate,
title: element.title,
description: element.description,
expired_date: element.expired_date,
expired_time: element.expired_time,
vStatus: element.vStatus
}
total_json.push(json);
}
});
}
}));
///i need the json printed here but it becomes null. how can i use async or promise here?
console.log(total_json)
i have seen lots of stack question, I just couldn't implement it in my code.
the problem was in your COMMON function. in the below example you can easily figure out what the issue is. why it prints an empty array because of the callback function.
let total_json = [];
let results = [
{ catName: "cat", id: "1", title: "title", description: "desc", cateID: "2" }
];
results.map(element => {
let body = { query: "" };
let query =
"select * from tb_voucher_category where id = " +
"'" +
element.cateID +
"'";
body.query = query;
COMMON(body, (err, results) => {
if (err) {
fatal_error.data = err;
return res.json(fatal_error);
}
if (results) {
if (results.length > 0) {
cate = results[0].cateName;
} else {
cate = "";
}
let json = {
id: element.id,
catename: cate,
title: element.title,
description: element.description
};
total_json.push(json);
}
});
});
function COMMON(data, func) {
setTimeout(() => {
func(null, [{ catName: "cat" }]);
});
}
console.log(total_json);
so I just add Promise.all to resolve this issue & I set element as a parameter in the COMMON function and return it from callback as newElement
let total_json = [];
let results = [
{ catName: "cat", id: "1", title: "title", description: "desc", cateID: "2" },
{
catName: "cat2",
id: "10",
title: "title2",
description: "desc2",
cateID: "20"
}
];
results.forEach(element => {
let body = { query: "" };
let query =
"select * from tb_voucher_category where id = " +
"'" +
element.cateID +
"'";
body.query = query;
total_json.push(
new Promise((resolve, reject) => {
COMMON(body, element, (err, results, newElement) => {
if (err) {
fatal_error.data = err;
return res.json(fatal_error);
}
if (results) {
if (results.length > 0) {
cate = results[0].cateName;
} else {
cate = "";
}
let json = {
id: element.id,
catename: cate,
title: element.title,
description: element.description
};
resolve(json);
}
});
})
);
});
function COMMON(data, element, func) {
setTimeout(() => {
func(null, [{ cateName: "cat" }], element);
});
}
Promise.all(total_json).then(res=>{
console.log(res)
})
Assuming you are using Express.js (inferred from res.json()), I suggest to isolate the database calls from the response.
function resultsToJSON(results) {
if (results.length > 0) {
cate = results[0].cateName;
} else {
cate = "";
}
let json = {
id: element.id,
catename: cate,
title: element.title,
description: element.description
};
return json;
}
// returns a single promise that resolves to a list of json results,
// or rejects as an error.
function getData() {
// `promises` is a list of Promises that either resolve to the database result,
// or rejects due to a database error.
const promises = results
.filter(element => element.start_date <= d && element.end_date >= d)
.map(element => {
let query = "select * from tb_voucher_category where id = " + "'" + element.cateID + "'";
body.query = query;
return new Promise((resolve, reject) => {
COMMON(body, (err, results) => {
if (err) { reject(err); }
else { resolve(results); }
});
});
});
return Promise.all(promises);
}
Then, in your Express request handler, you can handle the error (and send the error response), or get the total_jsons array from resolving the Promise.
// mark function as `async` if you are going to use `await` in the body.
app.get("/yourAPI", async (req, res) => {
// given that `getData()` returns a promise that resolves to a list of results,
// `await getData()` waits for the promise to be resolved, and returns the list of results.
try {
const total_json = await getData();
} catch (error) {
// handle a failure in *any* of your database requests
fatal_error.data = error;
res.json(fatal_error);
}
});
Related
I'm new at Node and what I'm trying to do here is call a function before the page rendered but my functions work after my page rendered and I can't get data coming from another function.
exports.getCity = (req, res) => {
controlCity(req.params.city).then((control) => {
res.render('index.ejs', control);
})
}
const controlCity = async (cityStub) => {
return new Promise((resolve, reject) => {
City.findAll({ where: { SEHIRSTUB: cityStub } })
.then(
city => {
if (city.length === 0) { // when it comes here it works
resolve({ value: "test" });
}
else {
resolve(controlPredictedWeather(city[0]));
}
}).catch(err => console.log(err));
}
)
}
const controlPredictedWeather = city => {
Predicted_Weather.findAll({ where: { CITYID: city.ID } }).then(
degree => {
if (degree.length === 0) {
return (getPredictedWeather(city)); // it goes another function
}
else {
console.log({ value: degree[0], city: city });
return { value: degree[0].degree, city: city };
}
}
).catch(err => console.log(err))
}
How can I solve this problem?
The issue is that you don't return anything in controlPredictedWeather function
const controlPredictedWeather = city => {
// vvv added return here
return Predicted_Weather.findAll({ where: { CITYID: city.ID } }).then(
degree => {
if (degree.length === 0) {
return (getPredictedWeather(city)); // it goes another function
}
else {
console.log({ value: degree[0], city: city });
return { value: degree[0].degree, city: city };
}
}
).catch(err => console.log(err))
}
Having solved the issue, now you can address the elephant in the room
you have an async function with no await
You're using the Promise constructor anti-pattern - i.e. new Promise where it's never required
Since you have used async keyword, that suggests you want to use the somewhat easier to read async/await pattern of using Promises
So, why not use it fully
Like this
exports.getCity = async (req, res) => {
const control = await controlCity(req.params.city);
res.render('index.ejs', control);
};
const controlCity = async (cityStub) => {
try {
const city = await City.findAll({ where: { SEHIRSTUB: cityStub } });
if (city.length === 0) { // when it comes here it works
return { value: "test" };
}
// no need for else since returning above
return controlPredictedWeather(city[0]);
} catch(err) {
console.log(err);
}
};
const controlPredictedWeather = async (city) => {
try {
const degree = await Predicted_Weather.findAll({ where: { CITYID: city.ID } });
if (degree.length === 0) {
return (getPredictedWeather(city)); // it goes another function
}
// no need for else since returning above
console.log({ value: degree[0], city: city });
return { value: degree[0].degree, city: city };
} catch(err) {
console.log(err)
}
};
Of course, there is one elephant left in the room, and that is that control in
res.render('index.ejs', control);
WILL be undefined if either of .findAlls throw an error - this will also be the case in your original code, just thought I'd mention that
I tried to make the code asynchronous but I couldn't. What i need to do?
This is my functions:
1.
router.post('/urls', (req, response) => {
count = 2;
webUrl = req.body.url;
depth = req.body.depth;
letstart(webUrl, response);
});
function letstart(urlLink, response) {
request(urlLink, function (error, res, body) {
console.error('error:', error); // Print the error if one occurred
console.log('statusCode:', res && res.statusCode); // Print the response status code if a response was received
//console.log('body:', body); // Print the HTML for the Google homepage.
if (!error) {
getLinks(body);
if (!ifFinishAll) {
GetinsideLinks(linkslinst, response);
}
else {
console.log("Finish crawl");
}
}
else {
console.log("sorry");
return "sorry";
}
});
}
function GetinsideLinks(list, response) {
count++;
if (count <= depth) {
for (let i = 0; i < list.length; i++) {
const link = list[i].toString();
var includeUrl = link.includes(webUrl);
if (!includeUrl) {
request(link, function (error, res, body) {
console.error('error2:', error); // Print the error if one occurred
console.log('statusCode2:', res && res.statusCode); // Print the response status code if a response was received
if (!error) {
getLinks(body);
}
else {
console.log("sorry2");
}
});
}
}
ifFinishAll = true;
}
else {
console.log("finish");
ifFinishAll = true;
response.status(200).send(resArray);
};
return resArray;
}
function getLinks(body) {
const html = body;
const $ = cheerio.load(html);
const linkObjects = $('a');
const links = [];
linkObjects.each((index, element) => {
countLinks = linkObjects.length;
var strHref = $(element).attr('href');
var strText = $(element).text();
var existUrl = linkslinst.includes(strHref);
var existText = textslist.includes(strText);
if (strText !== '' && strText !== "" && strText !== null && strHref !== '' && strHref !== "" && strHref !== null && strHref !== undefined && !existUrl && !existText) {
var tel = strHref.startsWith("tel");
var mail = strHref.startsWith("mailto");
var linkInStart = isUrlValid(strHref);
if (!tel && !mail) {
if (linkInStart) {
links.push({
text: $(element).text(), // get the text
href: $(element).attr('href'), // get the href attribute
});
linkslinst.push($(element).attr('href'));
textslist.push($(element).text());
}
else {
links.push({
text: $(element).text(), // get the text
href: webUrl.toString() + $(element).attr('href'), // get the href attribute
});
linkslinst.push(webUrl.toString() + $(element).attr('href'))
textslist.push($(element).text());
}
}
}
});
const result = [];
const map = new Map();
for (const item of links) {
if (!map.has(item.text)) {
map.set(item.text, true); // set any value to Map
result.push({
text: item.text,
href: item.href
});
}
}
if (result.length > 0) {
resArray.push({ list: result, depth: count - 1 });
}
console.log('res', resArray);
return resArray;
}
I want to return/response finally to the "resArray". I tried to add async and await to function number 1 and number 2 but it didn't succeed. Maybe I need to add async/await to all functions? How can I fix that?
You can achieve your goal by using async-await.
An async function is a function declared with the async keyword, and the await keyword is permitted within them. The async and await keywords enable asynchronous, promise-based behavior to be written in a cleaner style, avoiding the need to explicitly configure promise chains.
Basic example:
function resolveImmediately() {
return new Promise(resolve => {
resolve(true);
});
}
function resolveAfter2Seconds() {
return new Promise(resolve => {
setTimeout(() => {
resolve('resolved');
}, 2000);
});
}
async function asyncCall() {
console.log('calling');
const result = await resolveImmediately();
console.log(result);
if(result) {
const anotherResult = await resolveAfter2Seconds();
console.log(anotherResult);
}
}
asyncCall();
Note: Your code is too long to debug. As a result, to make you understand about the approach (what & how to do), i have added a simple example into my answer.
I'm doing a big loop once a day - which updating existing documents in the database (and also inserting new documents).
this loop get executed in a separate server ( prevents from the main server to be slow ), but the main problem is that all the find queries on the Data base (while the loop is executed) are very slow (the loop slows it down significantly).
This is a very big issue in my website ( this loop must be executed once a day ) and iv'e been trying to find a solution online - but i couldn't manage to find something.
Is there any way to prevent the find queries from being so slow while inserting/updating the database??
uploadProductsManually = async (name, products, map, valuesMap) => {
return new Promise(async function (resolve, reject) {
const company = await Company.findOne({ name }).exec();
if (!company) return reject(new errors.NotFound("Company not found"));
const rows = products;
const parsedRows = [];
const findCorrectKey = (key) => {
const correctKey = key.trim();
if (productFields[correctKey]) return productFields[correctKey];
const category = map.find((item) => {
return item.options.some((option) => {
return option.trim().toLowerCase() === correctKey.toLowerCase();
});
});
const categoryName = category && category.name;
return productFields[categoryName];
};
const hashProductValues = (product) => {
let valueToHash;
if (product.productId) {
valueToHash = product.productId;
} else if (product.certificateId) {
valueToHash = product.certificateId;
} else {
valueToHash = JSON.stringify(
product.size + product.color
);
}
return base64encode(valueToHash);
};
rows.forEach(function (row, i) {
var newProduct = {};
for (var key in row) {
var val = row[key];
if (val) {
let normalizedKey = findCorrectKey(key);
if (normalizedKey) {
newProduct[normalizedKey] = val;
}
let normalizedValue = normalizeValue(normalizedKey, val,valuesMap);
newProduct[normalizedKey] = normalizedValue;
}
}
newProduct.channels = [];
if (newProduct.productId) {
parsedRows.push(newProduct);
}
});
fetchProducts();
function fetchProducts() {
Product.find({ company: company._id }).exec(function (err, products) {
if (err) console.log(err);
var map = {};
if (products) {
products.forEach(function (product) {
const productIdentifier = hashProductValues(product);
map[productIdentifier] = product;
if (product.productStatus == "manual") {
// product.isAvailable = false;
// product.save();
} else {
product.status = "deleted";
product.save();
}
});
}
mergeData(map);
});
}
async function mergeData(map) {
let created = 0;
let updated = 0;
let manual = 0;
async.each(
parsedRows,
function (row, callback) {
const productIdentifier = hashProductValues(row);
let product = map[productIdentifier];
if (product) {
map[productIdentifier] = undefined;
Product.findByIdAndUpdate(id, { $set: updatedProduct }, function (
err,
updatedProd
) {
if (err) {
// errors.push(productIdentifier);
console.log("err is:", err);
}
updated++;
callback();
});
} else {
row = new Product(row);
row.save(function (err) {
if (err) {
// errors.push(productIdentifier);
console.log(err);
}
created++;
callback();
});
}
},
(err) => {
if (err) return reject(err);
Company.findByIdAndUpdate(
company._id,
{ lastUpdate: new Date() },
function (err, comp) {
if (err) console.log(err);
}
);
console.log(
`Created: ${created}\nUpdated: ${updated} \manual: ${manual}`
);
resolve({
created,
updated,
manual,
errors,
});
}
);
}
});
};
The problem is that when the second request is executed, it returns undefined, i.e. for some reason, it does not see the result of the second request. It should work like this: We make the first request, and if there are less than two lines, then we execute the second request. What could be the error? how to fix it
let arr = [name1, name2 /* ... */];
let ipObject = { Objects: [] };
arr.forEach(function(elem, index) {
connection.query("select 1 from i.goa where object_name = ?", elem, (err, rows) => {
// console.log (rows.length);
if (rows.length < 2) {
// console.log (elem);
connection.query(
"SELECT ip_adress FROM i.gs where server_kod=(SELECT server_kod FROM i.gol where object_kod =(SELECT object_kod FROM i.goa where object_name=?))",
elem,
(err, rows2) => {
console.log(elem);
console.log(rows2);
if (undefined !== rows2 && rows2.length > 0) {
// if(rows2.length>0 ){
ipObject.Objects.push({ objectName: elem, serverIp: rows2[0].ip_adress });
}
i++;
if (i > count) {
cb(JSON.stringify(ipObject));
console.log(JSON.stringify(ipObject));
// fs.writeFileSync('e.json',JSON.stringify(ipObject),'utf8');
}
},
);
} else if (rows.length >= 2) {
ipObject.Objects.push({ objectName: elem, serverIp: "ошибка" });
cb(JSON.stringify(ipObject));
}
});
});
You're probably bumping into asynchronicity issues here.
Refactoring things to use async/await and Promise.map(), maybe this is closer to what you want:
function queryP(connection, query, params) {
return new Promise((resolve, reject) => {
connection.query(query, params, (err, result) => {
if (err) {
return reject(err);
}
resolve(result);
});
});
}
async function queryForName(connection, objectName) {
const rows = await queryP(connection, "select 1 from i.goa where object_name = ?", objectName);
if (rows.length >= 2) {
return { objectName, serverIp: "ошибка" };
}
const rows2 = await queryP(connection, "SELECT ip_adress FROM i.gs where server_kod=(SELECT server_kod FROM i.gol where object_kod =(SELECT object_kod FROM i.goa where object_name=?))", objectName);
if (rows2.length > 0) {
return { objectName, serverIp: rows2[0].ip_adress };
}
return { objectName, serverIp: "???" };
}
async function queryForNames(connection, names) {
return {
Objects: await Promise.all(names.map((name) => queryForName(connection, name))),
};
}
// could as well be `const resultObject = await queryForNames(...)` if you're calling this from an async function.
queryForNames(connection, [name1, name2]).then((resultObject) => {
console.log(resultObject);
});
I'm writing jets tests and fully support unit test for dynamo express app. Is there a good example how mocking dynamo from my controller dependencies that it returns same response and verify the type of input data at dynamoDB low level api through the following functions attributes: get, query, batchGet, batchWrite as well as which data needs to be compared. The mock structure should behave as follows: controller -> db services -> json -> wrapper and thus be able to start asserting each case.
I tried aws-sdk-mock but unsuccessfully didn't work. Below is my code snippet:
function getRecordIdsFromDynamo(message) {
return new Promise((resolve, reject) => {
let is = message.id;
let params = {
Key: { id: id },
TableName: LOOKUP,
ConsistentRead: true,
ProjectionExpression: 'activeRecord'
}
dynamoClient.get(params, function(err, data) {
try {
let activeRecords = [];
if(data.Item.activeRecord)
activeRecords = data.Item.activeRecord.values;
resolve(activeRecords);
} catch(){}
})
})
}
function getRecordsFromDynamo(keys, id) {
return new Promise((resolve, reject) => {
let results = [];
let params = { RequestItems: {} };
if(keys.length == 0)
return resolve(results);
// unprocessed logic...
dynamoClient.batchGet(params, function(err, data) {
// err logic...
const responses = data.Responses[RECORD],
newUnprocessedKeys = data.UnprocessedKeys[RECORD];
results = results.concat(responses);
resolve(results)
})
})
}
exports.getRecords = function(message) {
return new Promise((resolve, reject) => {
getRecordsIdsFromDynamo(message)
.then((records) => {
let keys = records.map((id) => {
return { RecordId: id }
});
getRecordsFromDynamo(keys, message.id)
.then((records) => {})
.catch((err) => {
reject(err);
});
});
}
// test code
exports.getActiveRecordIds = async (message, docClient) => {
let data = await getActiveRecordsIdsfromDB(message, docClient);
console.log("data.Items =========>", data.Items);
return {
"Items": {
activeRecordIds: {
values: data.Items
}
}
}
};
/**
*
* #param {String} messageDevice
* #param {AWS} docClient
* #returns {Promise<object>} return promise with activeRuleIds
*/
function getActiveRecordsIdsfromDB(message, docClient) {
const activeRecords = db.getRecords(message);
console.log("In getActiveRecordsIdsFromDB " + activeRecords);
const params = {
Key: { id: message.id },
TableName: LOOKUP,
ConsistentRead: true,
ProjectionExpression: 'activeRecordIds'
};
return docClient.get(params).promise();
};
beforeEach(() => {
// Clear all instances and calls to constructor and all methods:
Message.mockClear();
event = record.event;
recordId = record.id;
ts = record.ts;
context = '1357';
message = new Message({
id: id,
ts: ts,
event: event
}, context);
});
it('should get all active records from dynamo', () => {
AWS_mock.mock('DynamoDB.DocumentClient', 'get', function(params, callback) {
callback(null, { Items: ['1234', '4321'] });
});
let docClient = new aws.DynamoDB.DocumentClient();
return getActiveRecordsIds(message, docClient)
.then(recordIds => {
expect(recordIds).toEqual({ Items: { activeRecordsIds: { values: ['1234', '4321'] } } });
});
});