Promise never settles - NodeJS - node.js

I've made the following loop, where I loop an Array; and then doing a quick GET request for each of them.
I made the loop work and the data is logged correctly. However the "loop" never finishes, and therefore not reaching ().then => {}
Promise.all(
arr.map((key,item) => {
return new Promise((resolve,reject) => {
var costmicro = 0;
request.post({
url: 'https://googleads.googleapis.com/v8/customers/'+arr[item]+'/googleAds:searchStream',
headers: {
'Content-Type': 'application/json',
},
json: {
"query": "SELECT metrics.cost_micros FROM campaign WHERE segments.date DURING LAST_30_DAYS",
}
}, function (errAdsCampaign, httpResponseAdsCampaign, bodyAdsCampaign) {
if (!errAdsCampaign) {
for (ii in bodyAdsCampaign[0].results) {
costmicro = costmicro+parseInt(bodyAdsCampaign[0].results[ii].metrics['costMicros']);
var arrlength = bodyAdsCampaign[0].results.length-1;
if (ii == arrlength) {
objectArray.push({name: arr[item], cost: costmicro / 1000000});
resolve(objectArray);
}
}
} else {
reject();
}
});
});
})
).then(()=>{
console.log('done');
console.log(objectArray)
}).catch(() => {
console.error;
});
UPDATE
Replaced the catch() function. Like so; sadly it never returns an error, and never finished
}).catch((e) => {
console.error(e);
});
Any help is much appreciated!

Inside your function inside the loop, you need to account for all possible exit paths and call either resolve() or reject(). One such exit path that is not handled is if bodyAdsCampaign[0].results is an empty array or undefined. In this case, the function will finish running without calling resolve() or reject().
You can also add a call to reject() at the end of your function (errAdsCampaign, httpResponseAdsCampaign, bodyAdsCampaign) callback function to catch all scenarios which aren't handled within the function.

Your code inside the loop likely threw an error and it skipped the .then() clause and went to the .catch() clause. You can replace the .catch() clause with the code below to see what error was encountered.
}).catch((e) => {
console.error(e);
});

The library request has been deprecated. Ref. https://www.npmjs.com/package/request
Instead, I would suggest using axios.
(async function run() {
try {
const objectArray = await Promise.all(
arr.map(async (key, item) => {
const res = await axios.post(
"https://googleads.googleapis.com/v8/customers/" +
arr[item] +
"/googleAds:searchStream",
{
json: {
query:
"SELECT metrics.cost_micros FROM campaign WHERE segments.date DURING LAST_30_DAYS",
},
},
{
headers: {
'Content-Type': 'application/json',,
},
}
);
const costmicro = res.data.bodyAdsCampaign.results.reduce((acc, cur) => {
acc += cur.metrics["costMicros"];
return acc;
}, 0);
return { name: arr[item], cost: costmicro / 1000000 };
})
);
console.log(objectArray);
} catch (err) {
console.log(err);
}
})();

Related

NodeJS: async/await doesn't return data correctly with loops

I'm having these calls to list users with groups from google active directory
let globalGroups = null;
let groupMembers = null;
await GetCustomerId(req, res).then( async () => {
// GetGroups is async function saves groups in `globalGroups` variable
await GetGroups(token).then( () => {
globalGroups.forEach( async (group) => {
// GetGroupMembers is async function saves users in `groupMembers` variable
await GetGroupMembers(group.id, token).then( () => {
groupMembers.forEach( (member) => {
// here I log the `member` and have no issues here
if (usersIds.includes(member.id)) {
let user = users.find( ({ id }) => id === member.id );
user.group_ids.push(group.id);
}
else {
member.group_ids = [];
member.group_ids.push(group.id);
users.push(member);
usersIds.push(member.id);
}
})
})
});
// the issue is here without timeout it returns an empty array because it doesn't wait for the loop to finish
console.log(users);
res.status(200).json({"users": users}).send();
}).catch(function(err) {
console.log(err)
res.status(500).json({"error": err}).send();
});
});
This returns an empty array unless I use timeout to return the response like this
setTimeout( () => {
console.log(users);
res.status(200).json({"users": users, "next_page_link": "notFound"}).send();
}, 1000);
How to make it wait till the whole loop ends to return the response without using timeout?
const GetCustomerId = async (req, res, next) => {
try {
let authorization = req.headers['authorization'].split(' ');
if (authorization[0] !== 'Bearer') {
return res.status(401).send();
} else {
await axios({
url: 'https://admin.googleapis.com/admin/directory/v1/users?domain=&maxResults=1',
method: 'get',
headers: {
'Content-Type': "application/json",
'Authorization': ' Bearer ' + authorization[1]
},
})
.then((response) => {
globalCustomerId = response.data.users[0].customerId
})
.catch(function(err) {
console.log(err);
});
}
} catch (err) {
console.log(err);
}
}
const GetGroups = async (token) => {
try {
await axios({
url: 'https://admin.googleapis.com/admin/directory/v1/groups?customer=' + globalCustomerId,
method: 'get',
headers: {
'Content-Type': "application/json",
'Authorization': ' Bearer ' + token
},
})
.then((response) => {
globalGroups = response.data.groups;
})
.catch(function (err) {
return res.status(500).json({"error": err}).send();
});
} catch (err) {
return res.status(403).json(err).send();
}
}
const GetGroupMembers = async (groupId, token) => {
await axios({
url: "https://admin.googleapis.com/admin/directory/v1/groups/" + groupId + "/members",
method: 'get',
headers: {
'Content-Type': "application/json",
'Authorization': ' Bearer ' + token
},
})
.then((response) => {
groupMembers = null;
groupMembers = response.data.members;
})
.catch(function (err) {
return res.status(500).json({"error": err}).send();
});
}
globalGroups.forEach( async (group) => {
An async method inside .forEach doesn't actually do what you might want it to do.
By essentially doing array.forEach(async method) you're invoking a bunch of async calls, 1 per element in the array. It's not actually processing each call one by one and then finally resolving.
Switch to using a regular for loop with await inside it and it will do what you want.
eg.
for (const group of globalGroups) {
await GetGroupMembers(group.id, token)
groupMembers.forEach.....
}
You could do that to force your code to be more synchronous (or use something like Promise.all to be more efficient while still being synchronous) but another issue with the code is you're stuck in callback hell, which leads to less-readable code.
I'd highly recommend refactoring your Get* methods such that they return the values you need. Then you can do something cleaner and predictable/deterministic like:
const globalCustomerId = await GetCustomerId(req, res);
const globalGroups = await GetGroups(token); //note: Promise.all could help here
for (const group of globalGroups) {
const groupMembers = await GetGroupMembers(group.id, token)
groupMembers.forEach.....
}
console.log(users);
res.status(200).json({"users": users}).send();
You can wrap it in a try/catch to take care of error handling. This leads to much cleaner, more concise, and more predictable order of executions.

NodeJS Elasticsearch search returns promise instead of values

I made a request inside my Node server to Elasticsearch. This is working perfect, except that I always get a promise returned instead of the results.
When I console log the results they look perfect, but when I return them I either get nothing or a promise.
Can someone tell me the proper way to retrieve, and handle the data from Elasticsearch?
I am using VueJS with a Node server and the official Elasticsearch package.
function getNewTest(client)
{
client.search({
index: 'myIndex',
}).then(function(resp) {
return resp.hits.hits;
}, function(err) {
console.trace(err.message);
});
}
let tests = getNewTest(client);
console.log(tests);
# Output: Promise { <pending> }
EDIT:
As suggested I tried both codes, both didnt work. I changed my own code, now it returns an "undefined" to me.
getNewTest(client).then(function (response) {
console.log(response);
});
will return "undefined" to me. I changed my function to this:
async function getNewTest(client)
{
await client.search({
index: 'myIndex',
}).then(function(resp) {
console.log(resp.hits.hits, 'returned');
return resp.hits.hits;
}, function(err) {
console.trace(err.message);
});
}
When I would do
let test = getNewTest(client);
it returns a promise to me.
(async () => {
let tests = await getNewTest(client);
console.log(tests);
})();
You are making a db call, so the main thread becomes free and start executing the next line. The code needs to wait till the promise is resolved and then execute the next line.
Or if you dont want to use async await, you can use this code below -
async function getNewTest(client) {
client.search({
index: 'myIndex',
}).then(function (resp) {
return resp.hits.hits;
}, function (err) {
console.trace(err.message);
});
}
getNewTest(client).then(result => {
console.log(result);
});
Function getNewTest will alway return undefined because you do not explicitly return anything.
Even if you did it this way :
function getNewTest(client)
{
return client.search({
index: 'myIndex',
}).then(function(resp) {
return resp.hits.hits;
}, function(err) {
console.trace(err.message);
});
}
It will return a promise.
const generatePromise = () => new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1500)
}, 500)
})
function test () {
return generatePromise()
.then((data) => console.log('after promise resolved ', data))
.catch((err) => console.log(err))
}
console.log('before calling test function');
const result = test()
console.log('after calling test function', result instanceof Promise);
When we call a function that returns a promise (async work) the execution does not wait for the promise to resolve it continue executing other code, that why const result = test() won't have the result of the promise.
The result of the promise will only be available inside the then handler as you can see in the code snippet.
const generatePromise = () => new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1500)
}, 500)
})
function test () {
return generatePromise()
}
console.log('before calling test function');
test()
.then((data) => console.log('after promise resolved ', data))
.catch((err) => console.log(err))
console.log('after calling test function');
You can achieve the required using async & await:
const generatePromise = () => new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1500)
}, 500)
})
// async function always returns promise even if you did't explicitly do
async function test () {
const data = await generatePromise();
// you can access resolved data here
console.log('after promise resolved ', data);
return data;
}
// first without then
console.log('before calling test function');
// you can't access it here unless you use then
// test().then(data => console.log(data));
const result = test();
console.log('after calling test function', result instanceof Promise);
This is how asynchronous works you can't return a promise and expect to receive the results instantly you can access the results inside the then handles or use await as I did.
When you do this:
async function getNewTest(client)
{
await client.search({
index: 'myIndex',
}).then(function(resp) {
console.log(resp.hits.hits, 'returned');
return resp.hits.hits;
}, function(err) {
console.trace(err.message);
});
}
it means this:
async function getNewTest(client)
{
await client.search({
index: 'myIndex',
}).then(function(resp) {
console.log(resp.hits.hits, 'returned');
return resp.hits.hits;
}, function(err) {
console.trace(err.message);
});
return undefined; // you are deliberately returning undefined
}
Remember that in javascript if you don't return anything the result of the function is undefined. I'm guessing what you intend to do is:
async function getNewTest(client)
{
return await client.search({ // NOTE THIS LINE
index: 'myIndex',
}).then(function(resp) {
console.log(resp.hits.hits, 'returned');
return resp.hits.hits;
}, function(err) {
console.trace(err.message);
});
}
Since .search() already returns a Promise (or promise-like object) you don't need to await for it. The code above is exactly the same as:
function getNewTest(client)
{
return client.search({ // NOTE THIS RETURN
index: 'myIndex',
}).then(function(resp) {
console.log(resp.hits.hits, 'returned');
return resp.hits.hits;
}, function(err) {
console.trace(err.message);
});
}
But still, this does not allow you to do let test = getNewTest(client). Nothing will ever make this possible. It is simply impossible. To get the result of getNewTest() either call it's .then() method or await for it. In other words, either do this:
getNewTest(client).then(function(test) { /*continue logic here*/ })
or do this:
async function foo () {
let test = await getNewTest(client);
/*continue logic here*/
}
foo();
Note that this mechanism applies everywhere. It is also impossible to do this:
async function foo () {
let test = await getNewTest(client);
return test;
}
let test = foo();
You must instead do this if you want to go this route:
async function foo () {
let test = await getNewTest(client);
return test;
}
async function bar () {
let test = await foo();
/*continue logic here*/
}
There is no escape. You can never directly return an asynchronous value.

How to refactor promises all chain using async await Node js and sequelize ORM

I want to refactor code for chain of promises by async, await. I have sequelize ORM for DB management and the code is written in AWS Lambda function having multiple middleware. In such cases I have to traverse code for multiple entries using sequelize transactions. It is easy to manage using promise.all() but need to change it to async await syntax for cleaner code.
Here are my demo code.
/* get all invoice where user_id === current logged in user, and where status != "paid" */
db.Invoice.findAll({
where: {
user_id: currentLoggedInUser,
status: {
$ne: "paid"
}
}
}).then(invoices => {
if (!invoices || invoices === null) {
return false;
}
function addScheduledTransactionAttempts(invoice, tryPayOnDate, t) {
return new Promise((resolve, reject) => {
/* check If any ScheduledTransactionAttempts exists for this invoice.id */
db.ScheduledTransactionAttempts.find({
where: {
invoice_id: invoice.id
}
})
.then(function(attempts) {
if (attempts) {
attempts
.destroy({}, {
transaction: t
})
.then(deletedAttempts => {
console.log("Attempts Record Deleted: ", deletedAttempts);
})
.catch(error => {
reject(error);
t.rollback();
});
}
return db.ScheduledTransactionAttempts.create({
invoice_id: invoice.id,
payment_source_id: PaymentMethodId,
try_pay_on_date: tryPayOnDate,
stripe_customer_id: currentLogInStripeCustomerId
}, {
transaction: t
})
.then(function(attempt) {
resolve(attempt.id);
})
.catch(error => {
reject(error);
t.rollback();
});
})
.catch(error => {
reject(error);
t.rollback();
});
});
}
//Run transaction to addScheduledTransactionAttempts
return db.sequelize.transaction().then(function(t) {
let promiseArr = [];
var i = 0;
invoices.forEach(function(invoice) {
var schedulePaymentDate = moment(paymentDate);
if (invoice) {
let tryPayOnDate = schedulePaymentDate
.add(i, "month")
.format("YYYY-MM-DD");
promiseArr.push(
addScheduledTransactionAttempts(invoice, tryPayOnDate, t) //calling above function
);
i++;
}
});
//now execute promise all
Promise.all(promiseArr)
.then(function(result) {
t.commit();
return true;
})
.catch(function(err) {
t.rollback();
return false;
});
});
});
In the above code I want to change
Promise.all(promiseArr)
which is calling
addScheduledTransactionAttempts
function to do DB queries to simple async function await process to make it easy simpler understandable without having multiple .then or .then inside then promises.
Any help regarding would be appreciated,
Thanks.
It's quite simple. await is valid when invoking methods that return a Promise. All of your SDK methods already return a promise, so refactoring should be quite straight forward. Here's something to get you off ground:
const processInvoices = async currentLoggedInUser {
const invoices = await db.Invoice.findAll({
where: {
user_id: currentLoggedInUser,
status: {
$ne: 'paid',
},
},
});
if (yourOwnLogicForInvoicesObject) {
for (const invoice of invoices) {
const potentiallyFoundInvoice = await db.ScheduledTransactionAttempts.find({
where: {
invoice_id: invoice.id,
},
});
if (potentiallyFoundInvoice) {
await addScheduledTransactionAttempts(potentiallyFoundInvoice)
}
}
}
}
const addScheduledTransactionAttempts = async invoice => {
console.log('Do something with your invoice', invoice)
}
Long story short: refactor the code inside your functions into smaller functions and make these new functions async, just like I did with addScheduledTransactionAttempts and processInvoices
More on async/await

Using async await properly in node js

To overcome callback hell in javascript, I'm trying to use async await from legacy code written in SQLServer procedure.
But I'm not sure my code might be write properly.
My first confusing point is when async function returns, should it return resolve() as boolean, or just return reject and handle with try-catch?
Here is my code snippets.
Please correct me to right direction.
apiRoutes.js
app.route('/api/dansok/cancelDansok')
.post(dansokCancelHandler.cancelDansok);
dansokCancelController.js
const sequelize = models.Sequelize;
const jwt = require('jsonwebtoken');
async function jwtAccessAuthCheck(accessToken) {
if (!accessToken) {
return Promise.reject('Empty access token');
}
jwt.verify(accessToken,"dipa",function(err){
if(err) {
return Promise.reject('TokenExpiredError.');
} else {
return Promise.resolve();
}
});
}
async function checkFeeHist(dansokSeqNo) {
let feeHist = await models.FeeHist.findOne({
where: { DansokSeqNo: dansokSeqNo}
});
return !!feeHist;
}
async function getNextDansokHistSerialNo(dansokSeqNo) {
....
}
async function getDansokFee(dansokSeqNo) {
....
}
async function doCancel(dansokSeqNo) {
try {
if (await !checkFeeHist(dansokSeqNo)) {
log.error("doCancel() invalid dansokSeqNo for cancel, ", dansokSeqNo);
return;
}
let nextDansokSerialNo = await getNextDansokHistSerialNo(dansokSeqNo);
await insertNewDansokHist(dansokSeqNo, nextDansokSerialNo);
await updateDansokHist(dansokSeqNo);
await updateVBankList(dansokSeqNo, danokFee.VBankSeqNo);
await getVBankList(dansokSeqNo);
} catch (e) {
log.error("doCancel() exception:", e);
}
}
exports.cancelDansok = function (req, res) {
res.setHeader("Content-Type", "application/json; charset=utf-8");
const dansokSeqNo = req.body.DANSOKSEQNO;
const discKindCode = req.body.HISTKIND;
const worker = req.body.PROCWORKER;
const workerIp = req.body.CREATEIP;
const accessToken = req.headers.accesstoken;
//check input parameter
if (!dansokSeqNo || !discKindCode || !worker || !workerIp) {
let e = {status:400, message:'params are empty.'};
return res.status(e.status).json(e);
}
try {
jwtAccessAuthCheck(accessToken)
.then(() => {
log.info("jwt success");
doCancel(dansokSeqNo).then(() => {
log.info("cancelDansok() finish");
res.status(200).json({ message: 'cancelDansok success.' });
});
});
} catch(e) {
return res.status(e.status).json(e);
}
};
You'll need to rewrite jwtAccessAuthCheck(accessToken) so that it keeps track of the outcome of its nested tasks. In the code you've written:
// Code that needs fixes!
async function jwtAccessAuthCheck(accessToken) {
// This part is fine. We are in the main async flow.
if (!accessToken) {
return Promise.reject('Empty access token');
}
// This needs to be rewritten, as the async function itself doesn't know anything about
// the outcome of `jwt.verify`...
jwt.verify(accessToken,"dipa",function(err){
if(err) {
// This is wrapped in a `function(err)` callback, so the return value is irrelevant
// to the async function itself
return Promise.reject('TokenExpiredError.');
} else {
// Same problem here.
return Promise.resolve();
}
});
// Since the main async scope didn't handle anything related to `jwt.verify`, the content
// below will print even before `jwt.verify()` completes! And the async call will be
// considered complete right away.
console.log('Completed before jwt.verify() outcome');
}
A better rewrite would be:
// Fixed code. The outcome of `jwt.verify` is explicitly delegated back to a new Promise's
// `resolve` and `reject` handlers, Promise which we await for.
async function jwtAccessAuthCheck(accessToken) {
await new Promise((resolve, reject) => {
if (!accessToken) {
reject('Empty access token');
return;
}
jwt.verify(accessToken,"dipa",function(err){
if(err) {
reject('TokenExpiredError.');
} else {
resolve();
}
});
});
// We won't consider this async call done until the Promise above completes.
console.log('Completed');
}
An alternate signature that would also work in this specific use case:
// Also works this way without the `async` type:
function jwtAccessAuthCheck(accessToken) {
return new Promise((resolve, reject) => {
...
});
}
Regarding your cancelDansok(req, res) middleware, since jwtAccessAuthCheck is guaranteed to return a Promise (you made it an async function), you'll also need to handle its returned Promise directly. No try / catch can handle the outcome of this asynchronous task.
exports.cancelDansok = function (req, res) {
...
jwtAccessAuthCheck(accessToken)
.then(() => {
log.info("jwt success");
return doCancel(dansokSeqNo);
})
.then(() => {
log.info("cancelDansok() finish");
res.status(200).json({ message: 'cancelDansok success.' });
})
.catch(e => {
res.status(e.status).json(e);
});
};
I strongly suggest reading a few Promise-related articles to get the hang of it. They're very handy and powerful, but also bring a little pain when mixed with other JS patterns (async callbacks, try / catch...).
https://www.promisejs.org/
Node.js util.promisify

How to chain promise in array

I need help with ES6 Promises chaining in array processing.
How to process/define each item of array which goes into Promise.all method, when there is other async method inside resolve?
Here is simplified example:
function getData(data, callback) {
let groupPromises = data.map(row => {
var coordinates = getCoordinates(row);
return Promise.resolve({
"place": getPlaces(coordinates), //how to invoke this method
"data": row
};
});
Promise.all(groupPromises)
.then(groups => callback(groups))
.catch(err => console.log(err));
}
}
function getPlaces(coordinates) {
return new Promise(function(resolve, reject) {
if(coordinates == null) {
reject();
}
parameters = {
location: [coordinates.latitude, coordinates.longitude],
rankby: "distance",
};
googlePlaces.searchPlace(parameters, function (error, response) {
if (error) {
reject(error);
};
resolve(response);
});
}
}
You can do it like this where you add a .then() handler to your first promise that gets the place and then when that's available returns the object you want. The resolved results of your Promise.all() will then be the array of objects you want:
function getData(data, callback) {
let groupPromises = data.map(row => {
var coordinates = getCoordinates(row);
// add .then() handler here to convert the place result
// into the object you want it in
return getPlaces(coordinates).then(place => {
return {place: place, data: row};
});
});
return Promise.all(groupPromises)
.then(groups => callback(groups))
.catch(err => {
console.log(err);
throw err;
});
}
}
function getPlaces(coordinates) {
return new Promise(function(resolve, reject) {
if(coordinates == null) {
reject();
}
parameters = {
location: [coordinates.latitude, coordinates.longitude],
rankby: "distance",
};
googlePlaces.searchPlace(parameters, function (error, response) {
if (error) {
reject(error);
};
resolve(response);
});
}
}
FYI, since you're converting over to promises, why not just return the promise from getData() and not use a callback there at all? Your current code has no way of communicating back an error from getData() which is something that comes largely for free with promises.
In fact with pure promises, getData() could be simplified to this:
function getData(data, callback) {
return Promise.all(data.map(row => {
return getPlaces(getCoordinates(row)).then(function(place) {
return {place: place, data: row};
});
}));
}

Resources