I try to push an array that I got from firestore, but when I try it's undefined
this is the code
const tmp = [];
const ref = firestore.collection("users").doc(user?.uid);
firestore
.collection("countrys")
.get()
.then((x) => {
x.forEach((y) => {
tmp.push(y.data());
});
});
console.log(tmp); //work
console.log(tmp[0]); //no work
this is the result from the console
as I see it's not same like another array
another array like (3) [{…}, {…}, {…}] ,but my array just show []
can someone explain to me why that's not working? thanks!
The issue here is that you've mixed asynchronous code with synchronous code.
To clarify further, firestore.collection("countrys").get() is a promise.
So if you want to interact with the data it returns just do it in your .then()
firestore
.collection("countrys")
.get()
.then((querySnapshot) => {
const tmp = [];
querySnapshot.forEach((doc) => {
tmp.push(doc.data());
});
console.log(tmp);
console.log(tmp[0]);
});
I would suggest doing some further reading into promises as well.
the console.log(temp) will execute before the
x.forEach((y) => {
tmp.push(y.data());
});
code will occur.
You need to add another .then chained to the then method so it will execute when the promise resolve to make the console log succeeded.
This is part of the event loop in JS.
I recommend to read more about it here:
https://blog.sessionstack.com/how-javascript-works-event-loop-and-the-rise-of-async-programming-5-ways-to-better-coding-with-2f077c4438b5
And more about promises here:
https://web.dev/promises/
Related
I'm having some issues with the "Promise.all" method.
Bascially, I have an array of URL (here is a simple one if you guys want to test :
const urlArray = [
"https://coverartarchive.org/release/985adeec-a1fd-4e79-899d-10c54b6af299",
"https://coverartarchive.org/release/4c54ee58-86df-3ba5-aaad-6b284293141b",
"https://coverartarchive.org/release/cd8e5736-ec8c-3c4d-a231-ac097877d87a",
"https://coverartarchive.org/release/b9b7641f-9389-342e-8be9-e463bd52fdb9",
"https://coverartarchive.org/release/b6206cad-15eb-3a95-b67e-1f49849e5fbd",
"https://coverartarchive.org/release/db425753-965f-4881-955b-8cd3ef65d1e6",
"https://coverartarchive.org/release/fa4f230a-e78c-32a8-bec8-3a7425aba9d2",
"https://coverartarchive.org/release/fa023617-1585-4ae6-81b6-1a07c47ecb2a",
"https://coverartarchive.org/release/61782e1c-67a2-487c-8324-6431c628cad8",
"https://coverartarchive.org/release/b16e94f3-ad3b-4e3b-9fad-0ef3d2a0958e",
"https://coverartarchive.org/release/37e7091f-9ebc-4ac8-875b-5c88f7e5fba8",
"https://coverartarchive.org/release/a63b6cc9-899c-447d-b0e6-d1e439379eb2",
"https://coverartarchive.org/release/d2d3df43-65c4-499e-90d2-22a157cc7dea",
"https://coverartarchive.org/release/9cb95cac-4a0d-4fbb-9237-544a99f29b57",
"https://coverartarchive.org/release/7cf87b52-47e3-4d12-8890-53a910792b70"
]
Normally, when a promise is resolved, it should return a JSON object, has seen when you enter one of those URLs above in your browser with infos about cover arts for an album release.
So I have tried using Promise.all using this array and see what goes, but I simply can't get something to work : the json() method. I have tried several ways to handle this, found on stackoverflow or elsewhere on the internet, I just always get the "Uncaught (in promise) TypeError: response.json is not a function"
Here are several things I tried :
Promise.all(urlArray)
.then(toJSON)
.then((jsonObjects) => console.log(jsonObjects));
function toJSON(responses) {
if (!Array.isArray(responses)) {
// also handle the non array case
responses = [responses];
}
return Promise.all(responses.map((response) => response.json()));
}
Same thing I guess but without helper
Promise.all(urlArray)
.then((res) => {
const responses = res.map((response) => response.json());
return Promise.all(responses);
})
.then((data) => console.log(data));
The worst thing is, I did manage to do it a few days ago, then changed my mind about how to go with this, and I just can't find how I solved this the first time around. If you guys have any idea what I'm doing wrong, don't hesitate to point it out and scold me about it !
Cheers
Your urlArray is an array of plain strings, not an array of requests. You never actually make any network requests in your code - you don't have any Promises.
Map the array of request URLs to an array of Promises first.
const urlArray = [
"https://coverartarchive.org/release/985adeec-a1fd-4e79-899d-10c54b6af299",
"https://coverartarchive.org/release/4c54ee58-86df-3ba5-aaad-6b284293141b",
"https://coverartarchive.org/release/cd8e5736-ec8c-3c4d-a231-ac097877d87a",
"https://coverartarchive.org/release/b9b7641f-9389-342e-8be9-e463bd52fdb9",
"https://coverartarchive.org/release/b6206cad-15eb-3a95-b67e-1f49849e5fbd",
"https://coverartarchive.org/release/db425753-965f-4881-955b-8cd3ef65d1e6",
"https://coverartarchive.org/release/fa4f230a-e78c-32a8-bec8-3a7425aba9d2",
"https://coverartarchive.org/release/fa023617-1585-4ae6-81b6-1a07c47ecb2a",
"https://coverartarchive.org/release/61782e1c-67a2-487c-8324-6431c628cad8",
"https://coverartarchive.org/release/b16e94f3-ad3b-4e3b-9fad-0ef3d2a0958e",
"https://coverartarchive.org/release/37e7091f-9ebc-4ac8-875b-5c88f7e5fba8",
"https://coverartarchive.org/release/a63b6cc9-899c-447d-b0e6-d1e439379eb2",
"https://coverartarchive.org/release/d2d3df43-65c4-499e-90d2-22a157cc7dea",
"https://coverartarchive.org/release/9cb95cac-4a0d-4fbb-9237-544a99f29b57",
"https://coverartarchive.org/release/7cf87b52-47e3-4d12-8890-53a910792b70"
]
Promise.all(
urlArray.map(
url => fetch(url).then(res => res.json())
)
)
.then((results) => {
console.log('got all results');
// use results here
});
here is what I am trying to do with firebase cloud function:
-Listen to any change in one of the documents under 'user' collection.
-Update carbon copies of the userinfo in the relevant documents in both 'comment' and 'post' collections.
Because I will need to query in relevant documents and update them at once, I am writing codes for transaction operations.
Here is the code that I wrote. It returns the error message, 'Function returned undefined, expected Promise or value'.
exports.useInfoUpdate = functions.firestore.document('user/{userid}').onUpdate((change,context) => {
const olduserinfo=change.before.data();
const newuserinfo=change.after.data();
db.runTransaction(t=>{
return t.get(db.collection('comment').where('userinfo','==',olduserinfo))
.then((querysnapshot)=>{
querysnapshot.forEach((doc)=>{
doc.ref.update({userinfo:newuserinfo})
})
})
})
.then(()=>{
db.runTransaction(t=>{
return t.get(db.collection('post').where('userinfo','==',olduserinfo))
.then((querysnapshot)=>{
querysnapshot.forEach((doc)=>{
doc.ref.update({userinfo:newuserinfo})
})
})
})
})
});
I am a bit confused because as far as I know, 'update' method returns a promise? I might be missing something big but I picked up programming only last November, so don't be too harsh. :)
Any advice on how to fix this issue? Thanks!
EDIT:
Building on Renaud's excellent answer, I created the below code in case someone may need it.
The complication with transaction is that the same data may be stored under different indices or in different formats. e.g. The same 'map' variable can be stored under an index in one collection, and as part of an array in another. In this case, each document returned by querying needs different update methods.
I resolved this issue using doc.ref.path, split, and switch methods. This enables application of different update methods based on the collection name. In a nutshell, something like this:
return db.runTransaction(t => {
return t.getAll(...refs)
.then(docs => {
docs.forEach(doc => {
switch (doc.ref.path.split('/')[0]) { //This returns the collection name and switch method assigns a relevant operation to be done.
case 'A':
t = t.update(doc.ref, **do whatever is needed for this collection**)
break;
case 'B':
t = t.update(doc.ref, **do whatever is needed for this collection**)
break;
default:
t = t.update(doc.ref, **do whatever is needed for this collection**)
}
})
})
})
Hope this helps!
Preamble: This is a very interesting use case!!
The problem identified by the error message comes from the fact that you don't return the Promise returned by the runTransaction() method. However there are several other problems in your code.
With the Node.js Server SDK you can indeed pass a query to the transaction's get() method (you cannot with the JavaScript SDK). However, in your case you want to update the documents returned by two queries. You cannot call twice db.runTransaction() because, then, it is not a unique transaction anymore.
So you need to use the getAll() method by passing an unpacked array of DocumentReferences. (Again, note that this getAll() method is only available in the Node.js Server SDK and not in the JavaScript SDK).
The following code will do the trick.
We run the two queries and transform the result in one array of DocumentReferences. Then we call the runTransaction() method and use the spread operator to unpack the array of DocumentReferences and pass it to the getAll() method.
Then we loop over the docs and we chain the calls to the transaction's update() method, since it returns the transaction.
However note that, with this approach, if the results of one of the two original queries change during the transaction, any new or removed documents will not be seen by the transaction.
exports.useInfoUpdate = functions.firestore.document('user/{userid}').onUpdate((change, context) => {
const olduserinfo = change.before.data();
const newuserinfo = change.after.data();
const db = admin.firestore();
const q1 = db.collection('comment').where('userinfo', '==', olduserinfo); // See the remark below: you probably need to use a document field here (e.g. olduserinfo.userinfo)
const q2 = db.collection('post').where('userinfo', '==', olduserinfo);
return Promise.all([q1.get(), q2.get()])
.then(results => {
refs = [];
results.forEach(querySnapshot => {
querySnapshot.forEach(documentSnapshot => {
refs.push(documentSnapshot.ref);
})
});
return db.runTransaction(t => {
return t.getAll(...refs)
.then(docs => {
docs.forEach(doc => {
t = t.update(doc.ref, { userinfo: newuserinfo })
})
})
})
})
});
Two last remarks:
I am not sure that db.collection('comment').where('userinfo', '==', olduserinfo); will be valid as olduserinfo is obtained through change.before.data(). You probably need to specify one field. This is probably the same for newuserinfo.
Note that you cannot do doc.ref.update() in a transaction, you need to call the transaction's update() method, not the one of a DocumentReference.
I am new to Adonis JS so extremely sorry for the Stupid Question.
I have the default setup of Adonis JS with Mysql Database and everything working.
I have created a simple usertest route where I am returning JSON of user with ID: 1.
Below is the code for the same
Route.get('/usertest', ({ response }) => {
const User = use('App/Models/User')
let data = User.query().where('id', 1)
.first()
console.log(data)
return response.status(200).json(data)
})
But it is returning and empty object
Raw Response:
{}
Console Log Statement Response:
Promise { <pending> }
I am unable to understand what am I missing here.
Note: I tried let data = User.find(1) but it did not work.
Please help me.
Thanks in advance!!!
Quick note, at least you have to execute the query asynchronously.
I mean, you have to replace:
let data = User.query().where('id', 1)
.first()
by:
let data = await User.query().where('id', 1)
.first()
Of course, this mean you have to precede the function arrow with async:
Route.get('/usertest', async ({ response }) => {
// rest of the code
let data = await User.query().where('id', 1).first()
// rest of the code
})
It is very easy to miss "await" when you start working with async-await frameworks. Just a suggestion on the query whenever you want to find one object it is good practice to use ORM's predefined function. In this case
const user = await User.findBy('id',1)
this will return the first object it finds with the given id. You will find more options and help in knex.js docs http://knexjs.org/#Builder-where
I know there are tons of questions and answers on using Javascript promises to load returned Firebase objects, but I haven't succeeded in capturing the data in an array.
The issue: I've set up an array (productArray below) to iteratively take in values of a Firebase snapshot, but the array remains empty after exiting the forEach loop.
database.js:
getProductList = function(){
let productArray =[];
loadFBData = function(){
firebase.initializeApp(config);
return firebase.database().ref().once("value", function(snapshot){
return snapshot.val();
});
}
Promise.all([loadFBData()]).then(function(snapshot) {
snapshot.forEach(function(product) {
productArray.push(product.val());
});
});
}
Question: I think I'm using Promise.all wrong, but can't figure out how (also am new asynchronous functions). Thanks for any hints on how to get productArray to successfully load.
Edit: To elaborate, it seems snapshot is loading with the correct Firebase data, and the forEach is correctly looping through each item in snapshot but productArray becomes (or remains) empty outside of the Promise.all statement.
Update I implemented all the suggestions below and yet, the program will not stop long enough to build an array from the snapshot object. Last year I used Angularfire's $loaded function which was so effective in getting the program to wait so I don't understand why an ordinary JS promise won't work here. Does it make sense to resort to timeout? I even tried to combine alexmac's and Pointy's suggestions, but no cigar:
getProductList = function(){
let productArray =[];
loadFBData = function(){
firebase.initializeApp(config);
return new Promise(resolve => {
firebase.database().ref().once('value')
.then(function(snapshot){
return resolve(function(snapshot){
productArray = Object.keys(snapshot).map((key, prod) => prod);
});
});
});
}
loadFBData();
}
Background
I think you're missing an understanding of both promises and the Firebase SDK.
First, note that all Firebase SDK functions return promises, so there's no need to create your own. Second, Promise.all is for combining multiple promises. A single promises can be chained off of with a simple .then().
I have a video on saving and querying data here: Save and Query Firebase Data
I have another video on promises here: Promises
Loading Firebase
You should be initializing firebase at the very top of your page. It only needs to be initialized once, so don't initialize it within a function.
You can call firebase.initializeApp(config) just after loading the Firebase SDK on the page. Or you can reference Firebase Hosting's init.js as shown below.
Example
The following example loads the Firebase SDK, uses my testing db's init.js file to initialize firebase, then queries, manipulates and returns an array.
Note that Firebase doesn't support zero-indexed arrays, so everything you get back from Firebase will be an unsorted object. This example shows a few different ways of returning the data :)
<html>
<head>
<script src="https://www.gstatic.com/firebasejs/4.3.0/firebase.js"></script>
<script src="https://quiver-two.firebaseapp.com/__/firebase/init.js"></script>
<script>
function loadFirebaseArray(path) {
return firebase.database().ref(path).once('value').then(snap => snap.val());
}
function toArray(items) {
return Object.keys(items).reduce((array, key) => {
const value = items[key];
array.push({key, value});
return array;
}, []).sort();
}
loadFirebaseArray('stackoverflow').then(items => {
console.log('items', items);
const asArray = toArray(items);
alert(JSON.stringify(asArray));
const justTheValues = asArray.map(x => x.value).sort();
alert(justTheValues);
});
</script>
</head>
</html>
A working example might look like this:
getProductList = () => {
let loadFBData = () => {
firebase.initializeApp(config);
return new Promise(resolve => {
firebase.database().ref().once('value', resolve);
});
}
let productArray =[];
loadFBData()
.then(snapshot => {
productArray = Object.keys(snapshot).map((key, prod) => prod);
}
Here, I use promisification to create a promise for firebase query. So loadFBData returns a promise, I can create a promise chain or use ES7 async/await to wait until promise will be fulfilled to use result.
I have three methods (each is a promise) that does SELECT from the database.
getDeliverDate()
.then(calculateOrderList)
.then(getOrderItemsForOrder)
For each deliverDate, I need to get the corresponding list of orders, and for each order I need to get the list of items. I will then build a JSONview to send to the client. To do that, I think I need to wrap the code above with a for loop, and presumably have a "global" object made of arrays etc.. and have to push to the object data from each method. I am not really sure if the approach will work. Can someone please tell me how to get it to work using the code above as an example.
Push all the promises into an array then use Promise.all to wait for all promises to finish and return the result. Something like
var promises = []
for (...) {
promises.push(getDeliverDate()
.then(calculateOrderList)
.then(getOrderItemsForOrder));
}
return Promise.all(promises);
Edit, all not when -- got my promise libraries confused
Better way to do that is use Promise.all:
Promise.all([getDeliverDate, calculateOrderList, getOrderItemsForOrder])
.then(values => {
console.log(values);
});
If what I understand is correct (as given in comments):
getDeliverDate()
// get array of delivery dates
.then((deliveryDates = []) => {
// for each delivery date, I need to get corresponding list of orders
const promises = deliveryDates.map(date => calculateOrderList(date));
return Promise.all(promises);
})
// get order list as an array
.then((orderList = []) => {
// for each order I need to get list of items corresponding to that order
const promises = orderList.map(order => getOrderItemsForOrder(order));
return Promise.all(promises);
})
// get item list as an array
.then((items) => console.log('got everything : ', items))
.catch((error) => console.log('error : ', error))
Looks like you need a lot of API calls. Probably we should think about optimizing backend.