How to make recursive promise calls? - javascript

I am working with an API that gives me data with a limit per request (here 25). Therefore, I have to recursively make promises with fetch. However, while most of my logic works, when I try to return from the function it will return an empty array. The image below will make it more clear.
const url = (conf_name) => {
return (
"https://api.elsevier.com/content/search/scopus?view=COMPLETE&cursor=*&query=CONFNAME(" +
conf_name +
")%20AND%20DOCTYPE(cp)%20AND%20SRCTYPE(p)&field=dc:creator,dc:title,dc:description,affilname,affiliation-city,affiliation-country,authkeywords,prism:doi,prism:doi,prism:coverDate"
);
};
const savePapers = (json, conf_name) => {
let temp = new Array();
for (let i = 0; i < json["search-results"]["entry"].length; i++) {
temp[i] = {
title: json["search-results"]["entry"][i]["dc:title"],
author: json["search-results"]["entry"][i]["dc:creator"],
publication_date: json["search-results"]["entry"][i]["prism:coverDate"],
doi: json["search-results"]["entry"][i]["prism:doi"],
abstract: json["search-results"]["entry"][i]["dc:description"],
author_keywords: json["search-results"]["entry"][i]["authkeywords"],
proceeding: conf_name,
};
}
return temp;
};
async function getPapers(final, url, conf_name) {
let total_amount_of_papers;
let next;
let position = 2;
try {
let response = await fetch(url, options);
let json = await response.json();
total_amount_of_papers = json["search-results"]["opensearch:totalResults"];
if (json["search-results"]["link"][position]["#ref"] == "prev")
next = json["search-results"]["link"][position + 1]["#href"];
next = json["search-results"]["link"][position]["#href"];
final = final.concat(savePapers(json, conf_name));
if (final.length === 50) {
console.log("hey",final.length);
return final;
}
await getPapers(final, next, conf_name);
} catch (error) {
console.log(error);
}
}
const createNewConf = async (conferences) => {
let final = new Array();
try {
var temp = new Conference({
acronym: conferences.acronym,
name: conferences.fullname,
area: conferences.area,
subarea: conferences.subarea,
location: conferences.location,
url: conferences.url,
description: conferences.description,
papers: await getPapers(final, url(conferences.acronym),conferences.acronym),
});
console.log(temp.papers.length);
} catch (error) {
console.log(error);
}
return temp;
};
describe("Saving records", function () {
it("Saved records to the database", async function (done) {
var conferences = [];
try {
for (var i = 0; i <= 1; i++) {
conferences[i] = await createNewConf(json_conferences[i]);
conferences[i].save().then(function () {
assert(conferences[i].isNew === True);
done();
});
}
mongoose.connection.close();
} catch (error) {
console.log(error);
}
});
});
Below you can see the length of my final array after passing the if to stop fetching more. and the second number is what I receive in the initial call
Console
Maybe anyone has an idea of the undefined behavior that occurs during return.
Your help is much appreciated.

Related

Return empty array in Fastify handler with async/await function

I'm trying to make a web3 wallet with Moralis with Fasitify in back and VueJS in front.
(I would like to say that I already do this in web client (vuejs) and everything works well!)
In my back, I fetch all the asset from user. It works, I get them all well and I can send them back to the front
But I need to process/sort the data to recover only part of it and send it to the front to avoid doing too much treatment at the front and reduce your workload.
I do that :
*handlers/token.js*
const getTokenBalances = async () => {
let userTokens;
const options = {
chain: "bsc",
address: "0x935A438C29bd810c9aBa2F3Df5144d2dF4F3c0A0",
};
userTokens = await Moralis.Web3API.account.getTokenBalances(options);
return userTokens;
};
and if I return userTokens, I had it in Postman. But now, I do something like :
const getTokenBalances = async (tokens) => {
let userTokens;
let userBalances = [];
const options = {
chain: "bsc",
address: "0x935A438C29bd810c9aBa2F3Df5144d2dF4F3c0A0",
};
userTokens = await Moralis.Web3API.account.getTokenBalances(options);
userTokens.forEach((token) => {
if (
!token.name.match(/^.*\.io/) &&
!token.name.match(/^.*\.IO/) &&
!token.name.match(/^.*\.net/) &&
!token.name.match(/^.*\.Net/) &&
!token.name.match(/^.*\.com/) &&
!token.name.match(/^.*\.org/)
) {
getTokenPair(token).then((value) => {
if (value > 2) {
checkTokenPrice(token).then((price) => {
if (price > 0.001) {
userBalances.push(token);
}
});
}
});
}
});
userBalances.sort((a, b) => {
return b.total_value_usd - a.total_value_usd;
});
return userBalances;
};
userBalances its always empty in my response in Postman (And I don't understand why ?)
The getTokenPair function :
const getTokenPair = async (token) => {
const BNBTokenAddress = "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c"; //BNB
const options = {
token0_address: token.token_address,
token1_address: BNBTokenAddress,
exchange: "pancakeswapv2",
chain: "bsc",
};
try {
const pairAddress = await Moralis.Web3API.defi.getPairAddress(options);
let amount = 0;
//console.log("token pairAddress : " + pairAddress.pairAddress);
if (pairAddress.token1.symbol === "BUSD") {
try {
let reserve = await getPairReserves(pairAddress.pairAddress);
amount += reserve / 10 ** 18;
} catch (err) {
console.log("error getReservesBUSD : " + err);
}
}
if (pairAddress.token1.symbol === "WBNB") {
try {
let reserve = await getPairReserves(pairAddress.pairAddress);
amount += reserve / 10 ** 18;
} catch (err) {
console.log("error getReservesWBNB : " + err);
}
}
//console.log("amount : " + amount)
return amount;
} catch (err) {
console.log("error getPair : " + err);
}
};
and the checkTokenPrice function :
const checkTokenPrice = async (token) => {
let price;
const options = {
address: token.token_address,
chain: "bsc",
exchange: "PancakeSwapv2",
};
try {
const priceToken = await Moralis.Web3API.token.getTokenPrice(options);
price = priceToken.usdPrice.toFixed(7);
token.price = priceToken.usdPrice.toFixed(7);
token.balance = insertDecimal(token.balance, token.decimals);
token.total_value_usd = (token.balance * token.price).toFixed(2);
return price;
} catch (err) {
console.log("error tokenPrice : " + err);
}
};
They are await method in all the function I use so maybe there is a connection.
Thanks for you help !
**** EDIT *****
I solve my problem like this :
const getTokenBalances = async () => {
let userTokens;
let userBalances = [];
const options = {
chain: "bsc",
address: "0x935A438C29bd810c9aBa2F3Df5144d2dF4F3c0A0",
};
userTokens = await Moralis.Web3API.account.getTokenBalances(options);
for (token of userTokens) {
if (
!token.name.match(/^.*\.io/) &&
!token.name.match(/^.*\.IO/) &&
!token.name.match(/^.*\.net/) &&
!token.name.match(/^.*\.Net/) &&
!token.name.match(/^.*\.com/) &&
!token.name.match(/^.*\.org/)
) {
let value = await getTokenPair(token);
if(value > 2) {
let price = await checkTokenPrice(token);
if (price > 0.001) {
userBalances.push(token);
}
}
}
}
return userBalances;
};
Thanks for the help ! ;)

Promise.all().then() gets executed before the promise.all() complete [duplicate]

This question already has answers here:
Promise.all is returning an array of undefined and resolves before being done
(3 answers)
Closed 25 days ago.
I'm trying to wrap my head around Promises, but my code doesn't work the way it should. My goal is to show that the table has loaded after all the promises are complete, but instead the code in then ("table loaded" and [undefined, undefined] 'res') get executed before. What is the way to make it work, waiting untill the table was filled?
My code
async function updateTable(accno, entity, start = null, funds) {
$table.bootstrapTable('removeAll')
prms = []
return Promise.all(
accno.map(
acc => {
let conf = {
method: 'get',
url: 'url',
params: {
account_number: acc,
start: start,
entity: entity,
}
}
axios(conf).then(function(response) {
let data = response.data
data_combined = []
for (let j = 0; j < data.return.length; j++) {
let row = data.return[j]
let temp = {
id: data.id,
// assigining more data here
}
data_combined.push(temp)
}
//another request deleted to simplify
fund_array = []
tableLoadAnimation('id-table-load', 'remove-all')
superset = [...superset, ...data_combined, ...fund_array]
$table.bootstrapTable('load', superset)
}).catch(function(error) {
console.log(error);
})
})
).then((a) => {
console.log('table loaded');
return a
}).then((res) => {
console.log(res, "res") // returns [undefined, undefined] 'res'
return res
})
}
Since axios returns promise, I had to return axios in map
async function updateTable(accno, entity, start = null, funds) {
$table.bootstrapTable('removeAll')
prms = []
prms = Promise.all(
accno.map(
acc => {
let conf = {
method: 'get',
url: 'url',
params: {
account_number: acc,
start: start,
entity: entity,
}
}
return axios(conf).then(function(response) {
let data = response.data
data_combined = []
for (let j = 0; j < data.return.length; j++) {
let row = data.return[j]
let temp = {
id: data.id,
// assigining more data here
}
data_combined.push(temp)
}
//another request deleted to simplify
fund_array = []
tableLoadAnimation('id-table-load', 'remove-all')
superset = [...superset, ...data_combined, ...fund_array]
$table.bootstrapTable('load', superset)
}).catch(function(error) {
console.log(error);
})
})
).then((a) => {
console.log('table loaded');
return a
})
}

How to measure the time of several calls of async function

I have a problem with understanding how do async functions in js work. So I need to call async function for 5 times and measure the duration of every execution. In the end I should have an array with 5 time values.
I have smth like this
for (let i = 0; i < 5; i++) {
const run = async () => {
await test(thisTest, filename, unitTestDict);
};
measure(run).then(report => {
// inspect
const {tSyncOnly, tSyncAsync} = report;
// array.push(tSyncAsync)
console.log(`∑ = ${tSyncAsync}ms \t`);
}).catch(e => {
console.error(e)
});
}
Here I found the realization of time measure function:
class Stack {
constructor() {
this._array = []
}
push(x) {
return this._array.push(x)
}
peek() {
return this._array[this._array.length - 1]
}
pop() {
return this._array.pop()
}
get is_not_empty() {
return this._array.length > 0
}
}
class Timer {
constructor() {
this._records = new Map
/* of {start:number, end:number} */
}
starts(scope) {
const detail =
this._records.set(scope, {
start: this.timestamp(),
end: -1,
})
}
ends(scope) {
this._records.get(scope).end = this.timestamp()
}
timestamp() {
return performance.now()
}
timediff(t0, t1) {
return Math.abs(t0 - t1)
}
report(scopes, detail) {
let tSyncOnly = 0;
let tSyncAsync = 0;
for (const [scope, {start, end}] of this._records)
if (scopes.has(scope))
if (~end) {
tSyncOnly += end - start;
tSyncAsync += end - start;
const {type, offset} = detail.get(scope);
if (type === "Timeout")
tSyncAsync += offset;
}
return {tSyncOnly, tSyncAsync}
}
}
async function measure(asyncFn) {
const stack = new Stack;
const scopes = new Set;
const timer = new Timer;
const detail = new Map;
const hook = createHook({
init(scope, type, parent, resource) {
if (type === 'TIMERWRAP') return;
scopes.add(scope);
detail.set(scope, {
type: type,
offset: type === 'Timeout' ? resource._idleTimeout : 0
})
},
before(scope) {
if (stack.is_not_empty) timer.ends(stack.peek());
stack.push(scope);
timer.starts(scope)
},
after() {
timer.ends(stack.pop())
}
});
return await new Promise(r => {
hook.enable();
setTimeout(() => {
asyncFn()
.then(() => hook.disable())
.then(() => r(timer.report(scopes, detail)))
.catch(console.error)
}, 1)
})
}
I can call it inside the loop and get console.log with time for every iteration:
measure(run).then(report => {
// inspect
const {tSyncOnly, tSyncAsync} = report;
// array.push(tSyncAsync)
console.log(`∑ = ${tSyncAsync}ms \t`);
}).catch(e => {
console.error(e)
});
But when I try to push this console values to array, nothing comes out, the array remains empty. Is there a way to fill it in?

How to return Boolean properly in different NodeJS files?

So I have files inside the following folder:
app/controller/token.js
app/controller/news.js
token.js:
"use strict";
var connection = require("../con");
exports.isTokenExists = function(token) {
var checkToken = "SELECT COUNT(`id`) AS 'total' FROM `user` WHERE `token` = '" + token + "'";
var isExists = false;
var count;
var checkResult;
connection.query(checkToken, function(error, rows) {
if (!error) {
checkResult = JSON.parse(JSON.stringify(rows));
for (var i = 0; i < checkResult.length; i++) {
var row = rows[i];
count = row.total;
}
if (count > 0) {
isExists = true;
}
}
});
return isExists;
};
news.js:
"use strict";
var response = require("../response/responses");
var connection = require("../con");
var getToken = require("./token");
exports.news = function(req, res) {
response.send(false, "News API", null, res);
};
exports.allNews = function(req, res) {
var checkTokenExists = getToken.isTokenExists("75d12cc4dc07608d5b87a6cba33cac056df1239c");
if (checkTokenExists) {
var allNewsQuery = "SELECT a.`id`, b.`title` AS `category`, a.`title`, a.`description`, a.`content`, a.`image`, a.`created_date` FROM `news` AS a LEFT JOIN `news_category` AS b ON a.`id_news_category` = b.`id` ORDER BY `created_date` DESC LIMIT 20";
connection.query(allNewsQuery, function(error, rows) {
if (error) {
response.send(true, "" + error, null, res);
} else {
var data = [];
var newsData = JSON.parse(JSON.stringify(rows));
for (var i = 0; i < newsData.length; i++) {
var row = rows[i];
data[i] = {
id: row.id,
idCategory: row.idCategory,
category: row.category,
title: row.title,
description: row.description,
image: row.image,
createdDate: row.created_date
};
}
response.send(false, "News is not empty", data, res);
}
});
} else {
response.send(true, "Error: Token not found", checkTokenExists, res);
}
};
I always getting false value from isTokenExists meanwhile the token is exists in the table.
How do I get true response if the token is exist and how do I get false response if the token is not exists in table?
Any help will be much appreciated.
Regards.
The issue here is that connection.query accepts a callback, but the rest of your code will move passed that without awaiting the result, which is why your isExists always returns false. You can fix this by encapsulating the query with a Promise like this:
"use strict";
const connection = require("../con");
exports.isTokenExists = async function(token) {
const checkToken = "SELECT COUNT(`id`) AS 'total' FROM `user` WHERE `token` = ?";
return new Promise((resolve, reject) => {
connection.query(checkToken, token, function (error, results) {
if (error) return reject(error);
return resolve(results.length > 0);
});
});
};
I also simplified the logic in the callback a bit.
Then, in news.js await the result like this:
exports.allNews = async function(req, res) {
getToken.isTokenExists("75d12cc4dc07608d5b87a6cba33cac056df1239c")
.then(result => {
if (result === true) {
//place your code for handling if the token exists here
}
else {
//place your code for handling if the token does not exist
}
})
.catch(err => {
//handle error
});
}
You are missing async / await concept. You need to wait until your query executes.
1) Write a promise function
export.getCount = function(query) {
return new Promise((res, rej) => {
let count = 0;
connection.query(checkToken, function(error, rows) {
if (!error) {
checkResult = JSON.parse(JSON.stringify(rows));
for (var i = 0; i < checkResult.length; i++) {
var row = rows[i];
count = row.total;
}
}
return res(count);
})
}
2) Write async function which supports await operations
exports.isTokenExists = async function(token) {
var query = "SELECT COUNT(`id`) AS 'total' FROM `user` WHERE `token` = '" + token + "'";
let count = await getCount(query)
return count > 0; // Returns true if count is > 0
};

Node js - function to return array of objects read from sequelize database

I'm trying to create a function in node js which reads database values in and pushes them into an array, and then at the end returns this array. My functions looks like this at the moment:
function getInvoicesCount() {
let promiseInvoices = [];
let userInvCount = 0;
let deletedUserInvCount = 0;
let userInvAmount = 0;
let deletedUserInvAmount = 0;
let monthWiseInvCount = [];
db.userInvoices
.findAll({
attributes: [
'deleted_at',
[sequelize.fn('COUNT', sequelize.col('id')), 'count'],
[sequelize.fn('SUM', sequelize.col('invoice_amount')), 'amount'],
[sequelize.fn('MONTH', sequelize.col('invoice_date')), 'month']
],
group: ['invoice_date', 'deleted_at'],
paranoid: false
})
.then(result => {
result.forEach(function(element) {
userInvCount += element.dataValues.count;
userInvAmount += element.dataValues.amount;
if (element.dataValues.deleted_at != null) {
deletedUserInvAmount += element.dataValues.amount;
deletedUserInvCount += element.dataValues.count;
}
monthWiseInvCount.push(element.dataValues);
});
if (monthWiseInvCount.map(a => a === 'deleted_at')) {
monthWiseInvCount.map(a => delete a.deleted_at);
}
promiseInvoices.push(
userInvCount,
userInvAmount,
deletedUserInvCount,
deletedUserInvAmount,
monthWiseInvCount
);
});
return promiseInvoices;
}
In the main part of the code I would like to call this funtion and use a .then to get the returned array
Can you help me out how I can return a promise in the function and how will the array be accessible in the .then part?
Here are the changes you need to do to get expected result :
function getInvoicesCount() {
...
return db.userInvoices.findAll({ //<-------- 1. First add return here
...
}).then(result => {
...
return promiseInvoices; //<----------- 2. Put this line inside the then
});
// return promiseInvoices; //<----------- Remove this line from here
}
getInvoicesCount().then(data => {
console.log(data); // <------- Check the output here
})
Explanation for this :
To get .then when you can function , function should return promise ,
but in you case you are just returning a blank array ,
As per the sequlize doc , db.userInvoices.findAll returns the
promise so all you need to do is add return before the this function
, First step is done
You were return promiseInvoices; at wrong place , why ? , coz at
that you will never get the result , as the code above it run in async
manner as it is promise , so you will always get the balnk array , to
get the expected result you should return it from
db.userInvoices.findAll's then function as shown above in code
You should return the results from then to access it in the chain.
function getInvoicesCount() {
const promiseInvoices = []
let userInvCount = 0
let deletedUserInvCount = 0
let userInvAmount = 0
let deletedUserInvAmount = 0
const monthWiseInvCount = []
return db.userInvoices
.findAll({
attributes: [
'deleted_at',
[sequelize.fn('COUNT', sequelize.col('id')), 'count'],
[sequelize.fn('SUM', sequelize.col('invoice_amount')), 'amount'],
[sequelize.fn('MONTH', sequelize.col('invoice_date')), 'month'],
],
group: ['invoice_date', 'deleted_at'],
paranoid: false,
})
.then((result) => {
result.forEach((element) => {
userInvCount += element.dataValues.count
userInvAmount += element.dataValues.amount
if (element.dataValues.deleted_at != null) {
deletedUserInvAmount += element.dataValues.amount
deletedUserInvCount += element.dataValues.count
}
monthWiseInvCount.push(element.dataValues)
})
if (monthWiseInvCount.map(a => a === 'deleted_at')) {
monthWiseInvCount.map(a => delete a.deleted_at)
}
return promiseInvoices.push(
userInvCount,
userInvAmount,
deletedUserInvCount,
deletedUserInvAmount,
monthWiseInvCount,
)
})
}
You can now use
getInvoicesCount().then(() => {
//do something here
})
getData(htmlData,tags)
.then(function(data) {
console.log(data); //use data after async call finished
})
.catch(function(e) {
console.log(e);
});
function getData() {
return new Promise(function(resolve, reject) {
//call async task and pass the response
resolve(resp);
});
}
In general you can return a promise like this:
function returnPromise() {
return new Promise((resolve, reject) => {
resolve({foo: 'bar'});
});
}
So while the other answer is completely correct, you can use the above structure to create a function that returns a promise like this:
function getInvoicesCount() {
return new Promise((resolve, reject) => {
let promiseInvoices = [];
let userInvCount = 0;
let deletedUserInvCount = 0;
let userInvAmount = 0;
let deletedUserInvAmount = 0;
let monthWiseInvCount = [];
db.userInvoices
.findAll({
attributes: [
'deleted_at',
[sequelize.fn('COUNT', sequelize.col('id')), 'count'],
[sequelize.fn('SUM', sequelize.col('invoice_amount')), 'amount'],
[sequelize.fn('MONTH', sequelize.col('invoice_date')), 'month']
],
group: ['invoice_date', 'deleted_at'],
paranoid: false
})
.then(result => {
result.forEach(function(element) {
userInvCount += element.dataValues.count;
userInvAmount += element.dataValues.amount;
if (element.dataValues.deleted_at != null) {
deletedUserInvAmount += element.dataValues.amount;
deletedUserInvCount += element.dataValues.count;
}
monthWiseInvCount.push(element.dataValues);
});
if (monthWiseInvCount.map(a => a === 'deleted_at')) {
monthWiseInvCount.map(a => delete a.deleted_at);
}
promiseInvoices.push(
userInvCount,
userInvAmount,
deletedUserInvCount,
deletedUserInvAmount,
monthWiseInvCount
);
resolve(promiseInvoices); // When you are done, you resolve
})
.catch(err => reject(err)); // If you hit an error - reject
});
}
However that is a rather big function, would recommend splitting it up in smaller parts.

Categories

Resources