I am very new to Firebase and I am not sure how to update the same field in all documents in one collection.
return db.collection('collection').get().then((snapshot) => {
return snapshot.forEach((doc) => {
doc.update({'field': 1});
});
})
.catch((err) => {
console.log('Error getting documents', err);
});
I am running this code in a firebase function. There are no errors, but nothing is happening. I can't see anything in the documentation about this specific problem. Does anyone know what's wrong?
You'll need to return the result of all the update() calls to the caller, as otherwise it can't know when all asynchronous operations have completed:
return db.collection('collection').get().then((snapshot) => {
return Promise.all(snapshot.documents.map((doc) => {
return doc.ref.update({'field': 1});
}));
});
The changes:
return the promise from update() so that it can be bubbled up to the Cloud Functions container.
Use map so that we get an array of promises.
Then use Promise.all so that we only complete (and thus terminate the Cloud Function) when all the updates have completed.
This approach did the thing for me... Don't know if this is a proper way (as said I am a beginner)
exports.scheduledFunction2 = functions.pubsub.schedule('every 1 minutes').onRun((context) => {
return db.collection("collection")
.get()
.then(querySnapshot => {
if (!querySnapshot.empty) {
for(let i =0;i<querySnapshot.size;i++){
var queryDocumentSnapshotTest = querySnapshot.docs[i];
queryDocumentSnapshotTest.ref.update({'counter':1});
}
return null;
} else {
// do nothing
console.log("No document corresponding to the query!");
return null;
}
});
});
Related
I have a function which fetch a data from Firestore :
getLastTime(collectionName: string) {
const docRef = this.afs.firestore.collection(collectionName).doc(this.User).collection('lastTime').doc('lastTime');
docRef.get().then(doc => {
if (doc.exists) {
this.get = doc.data().lastTime;
} else {
this.get = 'Never done';
}
}).catch(error => {
console.log('Error getting document:', error);
});
return this.get;
}
For my test I actually have a string value inside the doc 'lastTime' which is a string.
Inside ngOnInit(), I called my function and console.log the result
this.InjuredLastTime = this.getLastTime('INJURY');
console.log(this. this.InjuredLastTime);
Normally I should have my string printed inside the console but I got undefined...
Maybe it's because Firestore do not fetch fast enough my data, but I am quiet surprised since Firestore is quiet fast normally...
You don't actually wait for the promise that is created by docRef.get() before you return from getLastTime(). So, unless the call to firebase is instant (e.g. never) this won't work.
The correct solution really depends on what you are doing with this.InjuredLastTime. But one approach would just be to return a promise and set it after it is ready:
getLastTime(collectionName: string) {
const docRef = this.afs.firestore.collection(collectionName).doc(this.User).collection('lastTime').doc('lastTime');
return docRef.get().then(doc => {
if (doc.exists) {
return doc.data().lastTime;
} else {
return 'Never done';
}
}).catch(error => {
console.log('Error getting document:', error);
return null;
});
}
Then, instead of the assignment synchronously, do it asynchronously:
this.getLastTime('INJURY').then(result => { this.InjuredLastTime = result });
Data is loaded from Firestore asynchronously, since it may take some time before the data comes back from the server. To prevent having to block the browser, your code is instead allowed to continue to run, and then your callback is called when the data is available.
You can easily see this with a few well-placed log statements:
const docRef = this.afs.firestore.collection(collectionName).doc(this.User).collection('lastTime').doc('lastTime');
console.log('Before starting to get data');
docRef.get().then(doc => {
console.log('Got data');
});
console.log('After starting to get data');
If you run this code, you'll get:
Before starting to get data
After starting to get data
Got data
This is probably not the order that you expected the logging output in, but it is actually the correct behavior. And it completely explains why you're getting undefined out of your getLastTime function: by the time return this.get; runs, the data hasn't loaded yet.
The simplest solution in modern JavaScript is to mark your function as async and then await its result. That would look something like this:
async function getLastTime(collectionName: string) {
const docRef = this.afs.firestore.collection(collectionName).doc(this.User).collection('lastTime').doc('lastTime');
doc = await docRef.get();
if (doc.exists) {
this.get = doc.data().lastTime;
} else {
this.get = 'Never done';
}
return this.get;
}
And then call it with:
this.InjuredLastTime = await this.getLastTime('INJURY');
I thought I had a simple function:
database change trigger (.onUpdate)
find out which change is possibly important for a notification (prepareNotifications(change))
ask firebase if there are records that want a notification about that change (getDatabaseNotifications(changeWithNotification))
sent notifications (sentNotifications(changeWithNotification))
I'm stuck for a couple off days now on how to resolve the Firebase call before moving on.
tried to Promise.all([getDatabaseNotifications()])
tried to chain this function like this:
changes
then firebase call
then sent notifiactions
What is happening:
I get the changes,
The call to Firebase is done,
But before waiting for the result it moves on to sending notifications.
It finds no notifications in Firebase (but there are notifications!)
It's gathering the notifications (array [])
... here I push a test notification ...
It's sending 1 notification (the test notification)
Then it resolves the Firebase notifications (this should be resolved before).
Then the function stops without doing anything anymore.
This is how my function looks now. Can someone explain how I can wait on Firebase?
exports.showUpdate = functions.firestore
.document('shows/{showId}')
.onUpdate((change, context) => {
return prepareNotifications(change) // check if and which notifications to get out of the firebase database
.then(changes => {
console.log('changes');
console.log(changes);
if(changes) {
const gatherData = [];
changes.forEach(change => {
console.log('change');
console.log(change);
getDatabaseNotifications(change.showId, change.ring, change.ringNumber) // firebase call
.then(data => {
gatherData.push([...gatherData, ...data]);
console.log('gatherData');
console.log(gatherData);
})
.catch(err => {
console.log(err);
})
})
return gatherData;
}
return null;
})
.then(notifications => {
console.log('notifications');
console.log(notifications);
notifications.push(testData); // add some test notifications
if (notifications && notifications.length > 0) {
sentNotifications(notifications); // sent notifications
return 'sending notifications';
}
return 'no notifications to sent';
})
.catch(err => {
Sentry.captureException(new Error(`Showupdate sending notifications not ok. Error message: ${err.message}`));
})
});
Updated code which works! thanks to your examples.
exports.showUpdate = functions.firestore
.document('shows/{showId}')
.onUpdate((change, context) => {
return prepareNotifications(change) // check if and which notifications to get out of the firebase database
.then(changes => {
if(changes) {
return getDbRecords(changes);
}
})
.then(notifications => {
if (notifications && notifications.length > 0) {
sentNotifications(notifications); // sent notifications
return 'sending notifications';
}
return 'no notifications to sent';
})
.catch(err => {
Sentry.captureException(new Error(`Showupdate sending notifications not ok. Error message: ${err.message}`));
})
});
function getDbRecords(changes) {
const gatherData = [];
const gatherDataPromises = [];
changes.forEach(change => {
gatherDataPromises.push(
getDatabaseNotifications(change.showId, change.ring, change.ringNumber) // firebase call
.then(data => {
gatherData.push(...data);
})
);
});
return Promise.all(gatherDataPromises)
.then(() => { return gatherData }
);
}
This section of your code doesn't handle promises properly, it creates a bunch of work but then will return gatherData before any of it has happened, which is why you don't see any notifications:
if(changes) {
const gatherData = [];
changes.forEach(change => {
console.log('change');
console.log(change);
getDatabaseNotifications(change.showId, change.ring, change.ringNumber) // firebase call
.then(data => {
gatherData.push([...gatherData, ...data]);
console.log('gatherData');
console.log(gatherData);
})
.catch(err => {
console.log(err);
})
})
return gatherData;
}
Notably, you probably want that return gatherData to be chained off the set of promises that are generated by the entire set of calls to getDatabaseNotifications.
Something like:
if(changes) {
const gatherData = [];
const gatherDataPromises = [];
changes.forEach(change => {
console.log('change');
console.log(change);
gatherDataPromises.push(
getDatabaseNotifications(change.showId, change.ring, change.ringNumber) // firebase call
.then(data => {
gatherData.push([...gatherData, ...data]);
console.log('gatherData');
console.log(gatherData);
})
);
});
return Promise.all(gatherDataPromises)
.then(() => { return gatherData });
}
I removed the catch statement to allow the error to bubble up to the top level catch.
Caution: I have not tested this, as I don't have sample data or the code for getDatabaseNotifications, but the general approach should solve your current problem. Likewise, it allows all the calls to getDatabaseNotifications to run in parallel, which should be significantly faster than just awaiting on them in sequence.
That said, you do have other problems in this code -- for example, the return null just below the block I am discussing will likely lead you into trouble when you try to use notifications.push() in the following then() (but this also appears to be test code).
I think it's because of the async nature of the methods. So, instead of waiting "getDatabaseNotifications()" to finish it's job, it jumps into ".then(notifications =>{}" and in this case gatherData returns empty.
putting await before calling the method might work.
await getDatabaseNotifications(change.showId, change.ring, change.ringNumber)
This is my code snippet:
await firestore.runTransaction(t => {
t.get(docRef)
.then(docRef => {
logger.info("entering transaction");
if (!docRef.exists) {
t.create(docRef, new Data);
return Promise.resolve();
} else {
t.update(docRef, {aField: updateData});
return Promise.resolve();
}
})
.catch(error => {
return Promise.reject(error);
});
})
This gives me the following error:
Error: You must return a Promise in your transaction()-callback. at transaction.begin.then (/srv/node_modules/#google-cloud/firestore/build/src/index.js:496:32) at
It's my first time using firestore transaction syntax, and I know fairly about nodejs promise concepts and syntax. I mimiced the firestore transaction example here and wrote the above snippet.
After the error, I tried to change Promise.resolve() to Promise.resolve(0), but in vain.
I also tried to remove await and instead use .then, but it still gives me the same error.
Please shed some lights on it. Appreciate it. Thanks.
You need to basically return the Promise. Also, if t.create and t.update returns a promise as well, you can just return that. Don't do return Promise.resolve(), Which is wrong because it will resolve BEFORE actually inserting or creating.
Assuming everything works fine you need to do:
await firestore.runTransaction(t => {
return t.get(docRef)
.then(docRef => {
logger.info("entering transaction");
if (!docRef.exists) {
return t.create(docRef, new Data);
} else {
return t.update(docRef, { aField: updateData });
}
})
.catch(error => {
return Promise.reject(error);
});
})
Looking back the example, I realized i missed return in return t.get() but the error was in no way obvious to point out my mistake..
I've been trying to figure out what the proper way would be to write a promise for this function. I have an asynchronous function that makes an HTTP request to the server to retrieve a response, "documents_fileUploader." I am mapping through the "url" of each item within the response, and each url will go in to a function that makes another HTTP request and then sets the state. I want to fire the "upload()" function only after everything within the "documents_fileUploader()" function is complete. I tried doing this without a promise and it went straight to my "upload()" function because request was still pending. Any suggestions on how to go about this?
documents_fileUploader(formData).then(resp => {
resp.data.items.map(url => {
const key = url.split("/")[4];
this.setState({
urls: [...this.state.urls, url],
keys: [...this.state.keys, key]
});
this.getFileObject(key);
})
}).then(() => {
this.upload();
})
getFileObject = file => {
file_view(file).then(resp => {
this.setState({
mimeTypes: [...this.state.mimeTypes, resp.data.item.headers.contentType]
})
}).catch(err => {
console.log(err);
})
}
To your main question, you can wait for every promise that your .map call returns by using the Promise.all method.
Second, in order for that to work, your getFileObject function must return the promise it creates.
So incorporating those two changes, your snippet might look like:
documents_fileUploader(formData).then(resp => {
return Promise.all(resp.data.items.map(url => { // Wrap in Promise.all and return it
const key = url.split("/")[4];
this.setState({
urls: [...this.state.urls, url],
keys: [...this.state.keys, key]
});
return this.getFileObject(key); // Make sure to return this promise as well.
}));
}).then(() => {
// Now this won't happen until every `getFileObject` promise has resolved...
this.upload();
})
getFileObject = file => {
return file_view(file).then(resp => { // And return the promise here.
this.setState({
mimeTypes: [...this.state.mimeTypes, resp.data.item.headers.contentType]
})
}).catch(err => {
console.log(err);
})
}
I am trying to get a promise function with bluebirdjs. but all attempts falling my way because maybe I don't know what I am doing...?
I want to get files locations, then download the files one after the other then push to an array.
import * as Promise from 'bluebird';
fileFunction(files){
Promise.map(files, function(file) {
// Promise.map awaits for returned promises as well.
//Promise.delay(1000);
return this.getLocation(file);
},
).then((file) => {
console.log('done')
});
}
getLocation(file){
if(file){
return this._storage.ref(file).getDownloadURL().subscribe(url =>
this.img_array.push(url)
);
}
When I call the return this.getLocation(file)... I get the following error
bluebird.js:1545 Unhandled rejection TypeError: Cannot read property 'getLocation' of undefined ....bluebird.js
Edit part of the code am using now!
fileFunction(files){
return Promise.map(files, file => {
return this.getDownloadUrl(file);
}).then(locations => {
// add these locations onto this.img_array
this.img_array.push(...locations);
console.log('done');
return locations;
});
}
getFiles(e): Promise<any>{
this.outPutFiles = e;
this.fileFunction(this.outPutFiles).then(locations => {
locations.map((arr) => arr.subscribe((files) => this.downloadUrls.push(files)));
}).catch(err => {
console.log(err);
});
}
getDownloadUrl(file){
if(file){
return this._storage.ref(file).getDownloadURL();
} else {
return Promise.reject(new Error('No file passed to getLocation'));
}
}
this.getLocation(file) does not work because you've lost the value of this because you're inside a Promise.map() callback. Remember, that every normal function call in Javascript changes the value of this unless you specifically control the value of this.
You can fix that part of the issue with a simple arrow function for your callback like this:
fileFunction(files){
return Promise.map(files, file => {
return this.getLocation(file);
}).then(locations => {
// add these locations onto this.img_array
this.img_array.push(...locations);
console.log('done');
return locations;
});
}
This assumes that this.getLocation(file) returns a promise that resolves to the location value. Are you sure it does that? It looks like there may be more to your problem than just that first error you ran into.
And, after a side conversation, you also need to fix getLocation() to return a promise that resolves to the desired URL. Looking in the firebase Javascript doc, it appears that getDownloadURL() already returns a promise that resolves to the desired URL. So, you can just return that promise and let Promise.map() manage the results for you.
getLocation(file){
if(file){
return this._storage.ref(file).getDownloadURL();
} else {
return Promise.reject(new Error("No file passed to getLocation"));
}
}
And, then you would use it all like this:
obj.fileFunction(fileArray).then(locations => {
console.log(locations);
}).catch(err => {
console.log(err);
});