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

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
})
}

Related

About Promise Then

I wrote the following code to POST a mentioned message to Slack.
const resultSlack = [];
const selectedItems2 = 'XX : TESTS';
const getUserid = (value) => {
return new Promise(resolve => {
for (let i = 0; i < value.length; i++) {
let str = value[i].slice(5);
let body = {
app: XXX,
query: '"'+ str +'"'
};
kintone.api(kintone.api.url('/k/v1/records', true), 'GET', body, resp => {
resultSlack.push("<#" + resp.records[0].slackId.value + ">");
});
}
resolve(resultSlack);
});
};
getUserid(selectedItems2).then(results => {
console.log(results);
//There is an array
//0: "<#XXXXXXXX>"
//1: "<#YYYYYYYY>"
//2: "<#ZZZZZZZZ>"
//3: "<#SSSSSSSS>"
const payload = { text: results + "send already" };
//specify results in the payload, it will be ignored.
console.log(payload); // text: "send already"
});
Is it not possible to access the array passed as an argument with the then method?
How can I use the values ​​in the array in results? Please give me some advice.
Your code isn't right because it isn't properly waiting for the results from kintone.api() in your loop.
Fortunately for you, kintone.api() actually has the possiblity to return a promise! https://developer.kintone.io/hc/en-us/articles/212494388/
That means you can use async/await syntax inside your loop:
const getUserid = async (value) => {
const resultSlack = [];
for (let i = 0; i < value.length; i++) {
let str = value[i].slice(5);
let body = {
app: XXX,
query: '"'+ str +'"'
};
const resp = await kintone.api(kintone.api.url('/k/v1/records', true), 'GET', body);
resultSlack.push("<#" + resp.records[0].slackId.value + ">");
}
return resultSlack;
};
(warning: that code DOESN'T make all the looped requests simultaneously, which is possible if you feel you need it. It makes each request one at a time as written)
Now that that's written as an async function, you can do this:
getUserid(selectedItems2).then(results => ...
You have to wait for the responses to all the API calls to come in before resolving the promise.
A better way of doing this will be to transform the API calls into promises and resolve them all, here is an example:
const getUserid = (value) => {
const requests = [];
for (let i = 0; i < value.length; i++) {
let str = value[i].slice(5);
requests.push({
app: XXX,
query: '"'+ str +'"'
});
}
return Promise.all(requests.map((req) => new Promise(resolve => kintone.api(kintone.api.url('/k/v1/records', true), 'GET', req, resp => {
resolve("<#" + resp.records[0].slackId.value + ">");
})));
};
const getUserid = (value) => {
return new Promise(async resolve => {
for (let i = 0; i < value.length; i++) {
let str = value[i].slice(5);
let body = {
app: XXX,
query: '"'+ str +'"'
};
await kintone.api(kintone.api.url('/k/v1/records', true), 'GET', body, resp => {
resultSlack.push("<#" + resp.records[0].slackId.value + ">");
});
}
resolve(resultSlack);
});
};
When you calling an async function that makes a http request, you need to use await to wait until process done.

How to make recursive promise calls?

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.

Vue returns Observer in promise.then()

I want to fire a vuex action in created() and when I receive a data, then fire new asynchronous method that fetches more data from server. When data is available I will use them in a component. Unfortunatelly I got stuck with Observer returned from Promise. I tried to change data to computed() without luck. I tried to await but it did not help either. The other computed property item works fine. I know that the Observer is Vue's way for reactivity but I do not know how to fix it.
<SeriesBarChart v-if="! inProgress" :series="series" /> // initial attempt
<SeriesBarChart v-if="! inProgress" :series="groups" /> // computed property attempt
data: () => ({
series: [{}, {}],
inProgress: true,
}),
created() {
this.$store.dispatch('GET_POLL', { slug: this.slug }).then(() => {
this.runQueries(this.item._id, ['vehicles=car&vehicles=bike', 'region=PRG']); // await here did not help
});
},
computed: {
item() {
return this.$store.getters.POLL;
},
groups() {
return this.series;
},
},
methods: {
async runQueries(id, queries) {
this.inProgress = true;
const promises = [];
for (let i = 0; i < queries.length; i += 1) {
promises.push(this.$store.dispatch('GET_POLL_VOTES', { id, query: queries[i] }));
}
Promise.all(promises).then((values) => {
for (let i = 0; i < values.length; i += 1) {
this.series[i] = values[i].data.data;
}
});
this.inProgress = false;
}
Because Yom has not posted an answer and he even deleted his helpful comment, I will post my answer for future googlers. The reason why Vue provided the Observer object was a statement this.inProgress = false; outside of the then block. Following code works as expected:
async runQueries(id, queries) {
this.inProgress = true;
const promises = [];
for (let i = 0; i < queries.length; i += 1) {
promises.push(this.$store.dispatch('GET_POLL_VOTES', { id, query: queries[i] }));
}
Promise.all(promises).then((values) => {
for (let i = 0; i < values.length; i += 1) {
this.series[i] = values[i].data.data;
}
this.inProgress = false;
});
}

Wait for server response with axios from different file React

I have a loop. On each round I need to add Question data into MongoDB database. This works fine. However, I want to get _id of the new inserted Question before the loop goes into the next round. This is where I have a problem. It takes certain amount of time before the server returns _id and loop goes to the next round by that time. Therefore, I need a way to wait for the server response and only after that move to the next round of the loop.
Here is my back-end code:
router.post("/createQuestion", (req, res) => {
const newQuestion = new Question({
description: req.body.description,
type: req.body.type,
model: req.body.model
});
newQuestion.save().then(question => res.json(question._id))
.catch(err => console.log(err));
});
Here is my axios function, which is in a separate file and imported into the class:
export const createQuestion = (questionData) => dispatch => {
axios.post("/api/scorecard/createQuestion", questionData)
.then(res => {
return res.data;
}).catch(err =>
console.log("Error adding a question")
);
};
Here is my code inside my class:
JSON.parse(localStorage.getItem(i)).map(question => {
const newQuestion = {
description: question.description,
type: question.questionType,
model: this.props.model
}
const question_id = this.props.createQuestion(newQuestion);
console.log(question_id);
}
Console shows undefined.
i faced the same issue i solved the same by sending the array question to the node and read one by one question and update with the next Question ID.
router.post("/createQuestion", (req, res) => {
let d =[questionarray];
let i = 0;
let length = d.length;
var result = [];
try {
const timeoutPromise = (timeout) => new Promise((resolve) => setTimeout(resolve, timeout));
for (i = 0; i < length; i++) {
await timeoutPromise(1000); // 1000 = 1 second
let CAT_ID = parseInt(d[i].CAT_ID);
let TOPIC_ID = parseInt(d[i].TOPIC_ID);
let Q_DESC = (d[i].Q_DESC);
let OPT_1 = (d[i].OPT_1);
let OPT_2 = (d[i].OPT_2);
let OPT_3 = (d[i].OPT_3);
let OPT_4 = (d[i].OPT_4);
let ANS_ID = (d[i].ANS_ID);
let TAGS = (d[i].TAGS);
let HINT = (d[i].HINT);
let LEVEL = d[i].LEVEL;
let SRNO = d[i].SrNo;
let qid;
const savemyData = async (data) => {
return await data.save()
}
var myResult = await Question.find({ TOPIC_ID: TOPIC_ID }).countDocuments(function (err, count) {
if (err) {
console.log(err);
}
else {
if (count === 0) {
qid = TOPIC_ID + '' + 10001;
const newQuestion = new Question({
Q_ID: qid,
CAT_ID: CAT_ID,
TOPIC_ID: TOPIC_ID,
Q_ID: qid,
Q_DESC: Q_DESC,
OPT_1: OPT_1,
OPT_2: OPT_2,
OPT_3: OPT_3,
OPT_4: OPT_4,
ANS_ID: ANS_ID,
HINT: HINT,
TAGS: TAGS,
LEVEL: LEVEL,
Q_IMAGE: ''
})
await savemyData(newQuestion)
.then(result => { return true })
.catch(err => { return false });
//`${SRNO} is added successfully`
//`${SRNO} is Failed`
}
else if (count > 0) {
// console.log(count)
Question.find({ TOPIC_ID: TOPIC_ID }).sort({ Q_ID: -1 }).limit(1)
.then(question => {
qid = question[0].Q_ID + 1;
const newQuestion = new Question({
Q_ID: qid,
CAT_ID: CAT_ID,
TOPIC_ID: TOPIC_ID,
Q_ID: qid,
Q_DESC: Q_DESC,
OPT_1: OPT_1,
OPT_2: OPT_2,
OPT_3: OPT_3,
OPT_4: OPT_4,
ANS_ID: ANS_ID,
HINT: HINT,
TAGS: TAGS,
LEVEL: LEVEL,
Q_IMAGE: ''
})
await savemyData(newQuestion)
.then(result => { return true })
.catch(err => { return false });
})
.catch(err => console.log(err));
}
}
});
if (myResult)
result.push(`${SRNO} is added successfully`);
else
result.push(`${SRNO} is Failed`);
}
// console.log(result)
return res.json(result);
}
catch (err) {
//res.status(404).json({ success: false })
console.log(err)
}
});
First your function createQuestion doesn't return a value so the assigning to question_id would always be undefined. Anyways, since u have a dispatch in your createQuestion function, I am assuming u r using redux, so I would suggest you to using redux-thnk, split the fetching new action logic to a thunk action, and use the questionID value from the redux state rather than returning a value from createQuestion. In your class u can be listening for a change of the questionID and if that happens, dispatch the saving of the next question.

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