How do I process the results of more than one Promise?
Say the results a of DoA and b of DoB in DoNext
I read https://developer.mozilla.org/de/docs/Web/JavaScript/Guide/Using_promises
It introduces the "then"-Syntax which is supposed to replace the "callback pyramid of doom" however I don't understand the following:
DoA.then(function(a){
return DoB(a)
})
.then(function(b){
DoNext(a,b);
})
In the call of DoNext, a is unknown. I understand that this is because a is only defined in the anonymous function in line 2. However in the "callback pyramid of doom" I can access a, because in that pattern DoNext is within the anonymous function that is the success callback of DoA.
How do I handle this in then-Syntax?
There are multiple ways of handling multiple Promise situation.
1- Chain Promise (ugly though)
DoA.then(a => {
DoB(a).then(.....)
})
.catch(error => console.log(error));
2- Promise.all()
var promise1 = Promise.resolve(3);
var promise2 = 42;
var promise3 = new Promise(function(resolve, reject) {
setTimeout(resolve, 100, 'foo');
});
Promise.all([promise1, promise2, promise3]).then(function(values) {
console.log(values);
});
// expected output: Array [3, 42, "foo"]
3- async function (but read the details)
(async () => {
const a = await DoA(); // DoA must be a Promise
const b = await DoB(a); // DoB must be a Promise
// rest of the code
})();
Related
I will try to keep my problem as simple as possible, I have this function that I created:
get_total_by_status(status: string){
const total = imports.index(status).then((d) => {
return d.total
})
return total
}
and I'm calling this function like this:
var status_published = this.get_total_by_status("pending payment")
but it's not working, I have put a console.log(total) inside the function, and I got this:
Promise {<pending>}
[[Prototype]]:Promise
[[PromiseState]]: "fulfilled"
[[PromiseResult]]: 202
how can I return the 202 value??
I know that there are tons of questions similar to this one, and basically, all of them tell me to add async() at the function, and a await on the moment I call the function, I have tried this without success, and I have no idea what I'm missing here, I'm a python programer with 0 knowledge in react
Edit:
Tried this approach, still can't return the value
get_total_by_status(status: string, onSuccess) {
imports.index(status).then(
(d) => {
onSuccess(d.total);
}
);
}
# STUFFF
const status_published = this.get_total_by_status("published",
(response) => {
return response //also tried status_published = response
})
if I place a console.log(response) I do indeed can log the 202 that I was expecting, but status_published keeps being null
There are two options.
Option 1. Wrap your get_total_by_status call in an async function and await your get_total_by_status to get resolved value as follows:
async function get_total_by_status(status) {
const total = imports.index(status).then(
(d) => {
return d.total;
}
);
return total;
}
async function getPromiseValue() {
let status_published = await get_total_by_status("My status");
console.log(status_published);
}
getPromiseValue(); // prints "My status"
Option 2. Another option would be pass callback to your get_total_by_status function as an argument and call that callback in .then() block as follows:
function get_total_by_status(status, onSuccess) {
// you don't need to store any return value in this case
imports.index(status).then(
(d) => {
onSuccess(d.total);
}
);
}
get_total_by_status("My status", (response) => {
console.log(response); // prints "My status"
});
Let me know if you have any kind of query or doubt regarding above code snippets :)
The console.log in my indexedDB works and returns the result that I want; an array of objects that is currently in the store. So my code there is correct. I'm going to use this information to build a table. However, in Vue it returns undefined. I'm trying to set the leagues array in Vue to equal the result array that indexedDB gives, but it returns undefined.
This is the code in Vue:
<script>
import * as db from "../db/db.js";
export default {
name: "leaguesTable",
data: function() {
return {
leagues: []
};
},
created: function() {
this.leagues = db.getAllInStore("meta", "leagues");
console.log(this.leagues);
}
};
</script>
This is my indexedDB code:
function getAllInStore(dbName, storeName) {
let db;
var request = indexedDB.open(dbName, 1);
request.onerror = function(event) {
alert("Database error" + event.target.errorCode);
};
request.onsuccess = function(event) {
db = event.target.result;
let tx = db.transaction(storeName, "readonly");
tx.onerror = function(event) {
alert("Transaction error" + event.target.errorCode);
};
let store = tx.objectStore(storeName);
let result = store.getAll();
tx.oncomplete = function() {
alert("This should work");
console.log(result.result);
return result.result;
};
};
}
In your created hook you need to make sure to return a value from db.getAllInStore so that this.leagues assumes that value.
Next, In the getAllInStore function result.result gets returned from the transaction but not within onComplete or the enclosing getAllInStore function.
Since the db uses event hooks like onError and onComplete, Returning the request won't give you the result of the call to the db. In order to return the value of an async operation in javascript, typically callbacks or promises are used. The example below makes use of promises to solve the issue.
Vue JS:
<script>
import * as db from "../db/db.js";
export default {
name: "leaguesTable",
data: function() {
return {
leagues: []
};
},
// async is necessary to use await
created: async function() {
// await is es2016 syntactic sugar for retrieving the value of a promise
this.leagues = await db.getAllInStore("meta", "leagues");
console.log(this.leagues);
}
};
</script>
IndexDB:
function getAllInStore(dbName, storeName) {
// resolve param is a function that signifies a successful operation
// reject param is a function that should be called whenever a check or error occurs
return new Promise((resolve, reject) => {
let db;
let request = indexedDB.open(dbName, 1);
request.onerror = (event) => reject(event);
request.onsuccess = (event) => {
db = event.target.result;
let tx = db.transaction(storeName, "readonly");
request.onerror = (event) => reject(event);
let store = tx.objectStore(storeName);
let result = store.getAll();
tx.oncomplete = (result) => resolve(result.result);
};
});
}
Further Reading:
https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Asynchronous
https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Asynchronous/Promises
I have an array in which project _id is stored in the user collection. I'm initially fetching that array and then I'm trying to retrieve the data corresponding to the _id (stored in user collection) stored if projects collection. The data retrieved is pushed on to the array and that array is returned as an output.
But I'm unable to do so. An empty array is being returned.
var projectInfo= new Array();
users.post('/retrieveProjects', function(user, res, next) {
MongoClient.connect(url, function(err,db) {
if (err) throw err;
var dbo = db.db("EMWorks");
dbo.collection("users").findOne({_id:user.body.userid})
.then(response => {
if(response)
{
console.log(response);
(response.project).forEach(element => {
dbo.collection("projects").findOne({_id:element})
.then(respo => {
console.log(respo);
projectInfo.push(respo);
});
console.log(projectInfo) ;
});
}else{
console.log("No Projects created yet...!");
}
});
});
});
Output of the code
The problem is that dbo.collection("projects").findOne is async and returns a promise.
The forEach loop runs and generates these promises for each elemnt, but does not wait for its execution.
The promises are not resolved when the loop finishes and an empty array is returned.
You can wait for the promises to be executed and return the array only after that:
const promises = [];
(response.project).forEach(element => {
promises.push(dbo.collection("projects").findOne({_id:element})
.then(respo => {
console.log(respo);
projectInfo.push(respo);
});
}));
Promise.all(promises).then(function(values) {
console.log(projectInfo) ;
});
This can also be simplified with the map function and async/await :
const promises = (response.project).map(async element => {
const respo = await dbo.collection("projects").findOne({_id:element});
projectInfo.push(respo);
});
Promise.all(promises).then(function(values) {
console.log(projectInfo) ;
});
The problem
At web app startup, the main task is to retrieve all user informations that are stored in various Firestore's collections and sub-collections.
The problem is that, even if I uses promises and a Loading State to prevent an empty app rendering, the app is rendered with all collections data, except for collections that have sub-collections.
The process
- If user is logged
-- Set Loading Status Active
-- Load Collection A
-- Load Collection B and forEach, load all sub-collection
-- Load Collection C
-- Set Loading Status Inactive
At this point, the app is rendered, but only with Collection A and Collection C. The collection B is loaded (I can see it by Redux Logs) but can't be seen in the app.
Those datas appears only if I change the component status (open/close a menu, for example).
Some code
Here's how I retrieve a collection with sub-collections:
export function setCompanyJobs(user) {
return {
type: "SET_COMPANY_JOBS",
payload: loadCompanyJobs(user),
};
}
Function that retrieve the main collection
export function loadCompanyJobs(user) {
return new Promise((resolve, reject) => {
let companyJobs = [];
db.collection("company").doc(user.selectedCompany).collection("jobs").get().then((jobs) => {
jobs.forEach((job) => {
loadJobLinkedServices(user, job).then((jobLinkedServices) => {
companyJobs.push({
id: job.id,
...
});
});
});
resolve(companyJobs);
}).catch(function (error) {
...
});
});
}
Function that retrieve all collection's sub-collections
export function loadJobLinkedServices(user, job){
return new Promise((resolve, reject) => {
let jobLinkedServices = [];
db.collection("company").doc(user.selectedCompany).collection("jobs").doc(job.id).collection("linkedServices").get().then((linkedServices) => {
linkedServices.forEach((linkedService) => {
jobLinkedServices.push({
id: linkedService.id,
...
});
});
resolve(jobLinkedServices)
}).catch(function (error) {
...
});
})
When you do
return new Promise((resolve, reject) => {
let companyJobs = [];
db.collection("company").doc(user.selectedCompany).collection("jobs").get().then((jobs) => {
jobs.forEach((job) => {
loadJobLinkedServices(user, job).then((jobLinkedServices) => {
companyJobs.push({
id: job.id,
...
});
});
});
resolve(companyJobs);
}).catch(function (error) {
...
});
});
nothing ensure that your Promise resolves only after ALL the queries triggered in the jobs.forEach() loop are done (i.e. the promises returned by the calls to the loadJobLinkedServices function have resolved).
I don't know reactjs but I think you can use the JavaScript Promise.all() method along the following lines:
return new Promise((resolve, reject) => {
let promises = [];
let companyJobs = [];
db.collection("company").doc(user.selectedCompany).collection("jobs").get().then((jobs) => {
jobs.forEach((job) => {
promises.push(loadJobLinkedServices(user, job));
});
Promise.all(promises).
then(results => {
//Loop over the results array to populate the companyJobs array
resolve(companyJobs);
})
}).catch(function (error) {
...
});
});
Also, don't forget to correctly chain your calls to the different asynchronous functions, i.e. something like:
query Collection A
THEN query Collection B
THEN query all sub-collections (with Promise.all())
THEN query Collection C
THEN set Loading Status Inactive
Finally, a last remark: Note that the get() method returns a promise, so I am not sure that you need to wrap the calls to the get() method into some new Promises (Again, I am not versed in reactjs, so this remark may be wrong).
In other words, I think you could do something like the following (for example for the loadJobLinkedServices function):
export function loadJobLinkedServices(user, job){
let jobLinkedServices = [];
return db.collection("company").doc(user.selectedCompany).collection("jobs").doc(job.id).collection("linkedServices").get()
.then((linkedServices) => {
linkedServices.forEach((linkedService) => {
jobLinkedServices.push({
id: linkedService.id,
...
});
});
return jobLinkedServices;
}).catch(function (error) {
...
});
})
Any ideas? Why does node say 'filename is undefined'? Thanks.
Contract, policy ans invoice functions resolve with no data, just resolve().
var dc = function(data) {
return new Promise(function(resolve, reject) {
var filename = 'Test';
var contract = function() { ... }
var policy = function() { ... }
var invoice = function() { ... }
contract().then(invoice().then(policy().then(function() {
console.log(filename); // Test
resolve(filename); // UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 2): ReferenceError: filename is not defined
})))
})
}
First of all, you cannot write:
contract().then(invoice() ... )
(that would work if the invoice() function returned another function to act as a then handler)
You have to write:
contract().then(function (value) { invoice() ... })
Or:
contract().then(value => invoice() ... )
Or maybe this if one function should handle the result of other function:
contract().then(invoice).then(policy).then(function (result) { ... });
What you have to pass as an argument to then is a function, not a result of calling a function (which is probably a promise in your example).
I don't know if that's the only problem with your approach but it is certainly one of the problems. Of course it may work but probably not how you expect.
2017 Update
If you use ES2017 async/await that's available in Node since v7.0 then instead of:
contract().then(invoice).then(policy).then((result) => { ... });
you can use:
let a = await contract();
let b = await invoice(a);
let c = await policy(b);
// here your `result` is in `c`
or even this:
let result = await policy(await invoice(await contract()));
Note that you can only use it in functions declared with the async keyword. This works on Node since version 7. For older versions of Node you can use a similar thing with a slightly different syntax using generator-based coroutines, or you can use Babel to transpile your code if that's what you prefer of if that what you already do.
This is quite a new feature but there are a lot of questions on Stack Overflow about it. See:
try/catch blocks with async/await
Do async in a blocking program language way?
try/catch blocks with async/await
Use await outside async
Using acyns/await in Node 6 with Babel
When do async methods throw and how do you catch them?
using promises in node.js to create and compare two arrays
Keeping Promise Chains Readable
function will return null from javascript post/get
It looks like you don't care about the order, in which case you could use Promise.all. Does this work for you? It will resolve once all of the promises have been resolved, or it will reject as soon as any one of them rejects.
function contract(data) { return new Promise(...) }
function policy(data) { return new Promise(...) }
function invoice(data) { return new Promise(...) }
function dc(data) {
var filename = 'Test';
return new Promise(function(resolve, reject) {
Promise.all([contract(data), policy(data), invoice(data)]).then(
function (values) {
console.log(filename);
resolve(filename)
},
function (err) {
reject(err);
}
);
});
}
If you care about the order, then you do have to chain them, like you've tried to do. You're code is passing promises as an argument to then. You need to pass functions.
function contract(data) { return new Promise(...) }
function policy(data) { return new Promise(...) }
function invoice(data) { return new Promise(...) }
function dc(data) {
var filename = 'Test';
return new Promise(function(resolve, reject) {
contract(data).then(
function (contract_res) {
policy(data).then(
function (policy_res) {
invoice(data).then(
function (invoice_res) {
console.log(filename);
resolve(filename);
},
function (err) { reject(err); } // invoice promise rejected
);
},
function (err) { reject(err); } // policy promise rejected
);
},
function (err) { reject(err); } // contract policy rejected
);
});
}
You may be able to simplify this with catch, but the deep nesting is a pain. Take a look at this post about flattening Promise chains.