Calling async / await function and receiving a value back - javascript

I have got a function in an API library that calls firestore and gets data back. This part works fine:
export const getUserByReferrerId = async id => {
let doc = await firestore
.collection(FIRESTORE_COLLECTIONS.USERS)
.where('grsfId', '==', id)
.get()
.then(querySnapshot => {
if (!querySnapshot.empty) {
console.log ("we found a doc");
// use only the first document, but there could be more
const snapshot = querySnapshot.docs[0];
console.log ("snapshot", snapshot.id);
return snapshot.id // uid of the user
}
});
}
I am calling this library from a component. I have another function that runs on a button click. I cannot figure out how to get the value from the async api call.
I have tried this - a promise appears when I console.log the return:
testCreditFunction = (id) => {
let uid = getUserByReferrerId(id).then(doc => {
console.log("uid1", doc);
});
}
I have also tried this - log shows null for uid.
testCreditFunction = (id) => {
let uid = '';
(async () => {
uid = await getUserByReferrerId(id);
console.log ("uid in the function", uid);
})();
}
I have seen this question asked a few times and I have tried several of the answers and none are working for me. The odd thing is that I have done this same thing in other areas and I cannot figure out what the difference is.

Change your funtion to this.
export const getUserByReferrerId = async id => {
return await firestore
.collection(FIRESTORE_COLLECTIONS.USERS)
.where('grsfId', '==', id)
.get();
}
Try getting data this way.
testCreditFunction = (id) => {
let querySnapshot = '';
(async () => {
querySnapshot = await getUserByReferrerId(id);
const snapshot = querySnapshot.docs[0];
console.log ("snapshot", snapshot.id);
})();

One thing I notice right off the bat is that you're mixing async/await & .then/.error. While it's not strictly forbidden it definitely makes things more difficult to follow.
As others have mentioned in the comments, you need to make a return for the promise to resolve (complete). Here's how you might write getUserByReferrerId (using async/await).
const getUserByReferrerId = async id => {
const usersRef = firestore.collection('users');
const query = usersRef.where('grsfId', '==', id);
const snapshot = await query.get();
if (snapshot.empty) {
console.log('no doc found');
return;
}
console.log('doc found');
const doc = snapshot.docs[0]; // use first result only (there might be more though)
return doc.id
}
You'll notice how I split up the query building steps from the snapshot request. This was intentional as the only promised function in this is the get request.
Using this getUserByReferrerID can be done with a simple await. Just be sure to check that the result isn't undefined.
const userID = await getUserByReferrerId('someid')
if (!userID) {
console.log('user not found');
}
console.log(`User ID is ${userID}`);
p.s - I would recommend renaming the function to getUserIdByReferrerId to more accurately reflect the fact that you're returning an ID and not a user doc. Alternatively, you can return the user object and leave the name as is.

Related

FindOne inside map returns no results

I'm trying to do a search using FindOne inside map but it never finds the data by Product Id. I don't understand the reason. Im using express on nodejs.
This is my code:
const calc = (details) => {
let grandSubtotal = 0;
details.map( async detail => {
const {verifyProduct} = await Product.find({ _id: detail._id});
console.log(detail._id);
console.log(verifyProduct); // UNDEFINED
...
Should be:
const result = await Promise.all(details.map( async (detail) => { … } ));
when you do it like you done you will get a pending promise object that never going to be resolved, I don’t know if you want to return some results, if no just do await Promise.all
Also this should be:
const calc = async (details) => { … }
Mongoose find returns a list of results. findOne returns an object.
The code is doing the equivalent of:
const {verifyProduct} = []
Use findOne to get an object to destructure, and test the result before use.
details.map( async (detail) => {
const res = await Product.findOne({ _id: detail._id });
if (!res) {
//throw new Error('No id '.detail._id)
console.log('No id', detail._id)
}
const { verifyProduct } = res
console.log(detail._id);
console.log(verifyProduct);
}
Also (as #antokhio noted), if you want to use the returned result array of the details.map you will need to await those as well.
You don't need await here
Product.find(({ _id }) => _id === detail._id );

Pulling a variable out of Promise when querying data from Firestore Firebase database - Javascript

let savedArrayUID = []; let savedArrayEmails = [];
function pullIt(emailSearch) {
db.collection(collectionName).where('email', '==', emailSearch).get()
.then((querySnapshot) => {
querySnapshot.forEach((doc) => {
savedArrayUID.push(doc.id);
savedArrayEmails.push(doc.data());
// doc.data() is never undefined for query doc snapshots
console.log(doc.id, " => ", doc.data());
// saved. push(doc.id);
return savedArrayUID;
})
});
}
I can query the data from the database but cannot pull the variable out of the scope of the function.
I want to use this function to pass through emails to find info of their profile saved in my Database.
I really struggle to understand how Promiseses can help here. I have a feeling this is already solved, but I could not find an answer anywhere.
There's two steps to this:
Ensure that your data makes it out of your pullIt (as a promise).
Then call that pullIt correctly, waiting for the promise to resolve.
In code, you're missing a top-level return the pullIt code:
function pullIt(emailSearch) {
// πŸ‘‡
return db.collection(collectionName).where('email', '==', emailSearch).get()
.then((querySnapshot) => {
querySnapshot.forEach((doc) => {
savedArrayUID.push(doc.id);
savedArrayEmails.push(doc.data());
})
return savedArrayUID; // πŸ‘ˆ
});
}
And then when calling pullIt, you'll need to use either await or then, to ensure pullIt completed before you try to access the result.
So either:
pullIt("yourSearchTeam").then((results) => {
// TODO: use your results here
})
Or (in an async context):
const results = await pullIt("yourSearchTeam")
// TODO: use your results here

Movie API:How Can I Return The Value?

I'm using The Movie Database API. And the problem that i can't solve is returning "keys" variable when i call the function with Movie's id.I'm new on JavaScript that's why i can't solve this. Hope someone can help me, thanks in advance.
const APIURL = "https://api.themoviedb.org/3/discover/movie?sort_by=popularity.desc&api_key=[MY API KEY HERE]&page-1";
getMovies(APIURL)
async function getMovies(url)
{
const resp = await fetch(url);
const respData = await resp.json();
showMovies(respData.results)
}
async function getTrailer(id)
{
const resp = await fetch(`https://api.themoviedb.org/3/movie/${id}/videos?api_key=[MY API KEY HERE]&language=en-US`);
const respDataa = await resp.json();
let results = respDataa.results;
let keys = results[0].key;
return keys;
}
function showMovies(movies){
movies.forEach(movie => {
const modals = document.createElement('div');
modals.classList.add('modal');
modals.innerHTML = ` <a target="_blank" href ="https://www.youtube.com/watch?v=${getTrailer(movie.id)}">Watch Trailer</a>`
}
}
Well, first of all, always hide your api keys if you're posting your code somewhere (even if it's a private repository, you shouldn't do it).
Secondly, if you want to return multiple keys, you can map the results to extract id from each of them and do a return:
async function getTrailer(id)
{
const resp = await fetch(`https://api.themoviedb.org/3/movie/${id}/videos?api_key=04c35731a5ee918f014970082a0088b1&language=en-US`);
const respDataa = await resp.json();
let results = respDataa.results;
return results.map(({ key }) => key);
}
Async functions return a Promise in JavaScript.
Simply add return keys at the end of your function.
Then you can do:
getTrailer(528085).then(data => {
// do something with the data
})
You can also handle errors:
getTrailer(528085)
.then(data => {
// do something with the data
})
.catch(err => {
/*
An error occured. You can use err.toString() to convert the error into a string
*/
})
If you want to get the returned data immediately from an async function(or a promise), put your logic inside an async function, then you can simply do:
let keys = await getTrailer(528085)
And, here is how to handle errors in async/await:
try {
let keys = await getTrailer(528085)
}
catch(err){
/*
An error occured. You can use err.toString() to convert the error into a string
*/
}
By the way, like Desiigner said, don't keep your API keys in the client. Anyone can see your API key. Use a server to return the API response to the client.
We have to await or.then the return (it’s a Promise).
function showMovies(movies) {
// make the forEach callback async
movies.forEach(async (movie) => {
console.log(getTrailer(movie.id)) // Promise {<pending>}
const trailer = await getTrailer(movie.id);
console.log({ trailer });
const modals = document.createElement("div");
modals.classList.add("modal");
modals.innerHTML = ` <a target="_blank" href ="https://www.youtube.com/watch?v=${trailer}">Watch Trailer</a>`;
});
}

How to get fetch api results in execution order with async/await?

After an input change in my input element, I run an empty string check(if (debouncedSearchInput === "")) to determine whether I fetch one api or the other.
The main problem is the correct promise returned faster than the other one, resulting incorrect data on render.
//In my react useEffect hook
useEffect(() => {
//when input empty case
if (debouncedSearchInput === "") autoFetch();
//search
else searchvalueFetch();
}, [debouncedSearchInput]);
searchvalueFetch() returned slower than autoFetch() when I emptied the input. I get the delayed searchvalueFetch() data instead of the correct autoFetch() data.
What are the ways to tackle this? How do I queue returns from a promises?
I read Reactjs and redux - How to prevent excessive api calls from a live-search component? but
1) The promise parts are confusing for me
2) I think I don't have to use a class
3) I would like to learn more async/await
Edit: added searchvalueFetch, autoFetch, fetcharticles code
const autoFetch = () => {
const url = A_URL
fetchArticles(url);
};
const searchNYT = () => {
const url = A_DIFFERENT_URL_ACCORDING_TO_INPUT
fetchArticles(url);
};
const fetchArticles = async url => {
try{
const response = await fetch(url);
const data = await response.json();
//set my state
}catch(e){...}
}
This is an idea how it could looks like. You can use promises to reach this. First autoFetch will be called and then searchvalueFetch:
useEffect(() => {
const fetchData = async () => {
await autoFetch();
await searchvalueFetch();
};
fetchData();
}, []);
You can also use a function in any lifecycle depends on your project.
lifecycle(){
const fetchData = async () => {
try{
await autoFetch();
await searchvalueFetch();
} catch(e){
console.log(e)
}
};
fetchData();
}
}

Getting all documents from one collection in Firestore

Hi I'm starting with javascript and react-native and I'm trying to figure out this problem for hours now. Can someone explain me how to get all the documents from firestore collection ?
I have been trying this:
async getMarkers() {
const events = await firebase.firestore().collection('events').get()
.then(querySnapshot => {
querySnapshot.docs.map(doc => {
console.log('LOG 1', doc.data());
return doc.data();
});
});
console.log('LOG 2', events);
return events;
}
Log 1 prints all the objects(one by one) but log 2 is undefined, why ?
The example in the other answer is unnecessarily complex. This would be more straightforward, if all you want to do is return the raw data objects for each document in a query or collection:
async getMarker() {
const snapshot = await firebase.firestore().collection('events').get()
return snapshot.docs.map(doc => doc.data());
}
if you want include Id
async getMarkers() {
const events = await firebase.firestore().collection('events')
events.get().then((querySnapshot) => {
const tempDoc = querySnapshot.docs.map((doc) => {
return { id: doc.id, ...doc.data() }
})
console.log(tempDoc)
})
}
Same way with array
async getMarkers() {
const events = await firebase.firestore().collection('events')
events.get().then((querySnapshot) => {
const tempDoc = []
querySnapshot.forEach((doc) => {
tempDoc.push({ id: doc.id, ...doc.data() })
})
console.log(tempDoc)
})
}
I made it work this way:
async getMarkers() {
const markers = [];
await firebase.firestore().collection('events').get()
.then(querySnapshot => {
querySnapshot.docs.forEach(doc => {
markers.push(doc.data());
});
});
return markers;
}
if you need to include the key of the document in the response, another alternative is:
async getMarker() {
const snapshot = await firebase.firestore().collection('events').get()
const documents = [];
snapshot.forEach(doc => {
const document = { [doc.id]: doc.data() };
documents.push(document);
}
return documents;
}
The docs state:
import { collection, getDocs } from "firebase/firestore";
const querySnapshot = await getDocs(collection(db, "cities"));
querySnapshot.forEach((doc) => {
// doc.data() is never undefined for query doc snapshots
console.log(doc.id, " => ", doc.data());
});
However I am using the following (excuse the TypeScript):
import { collection, Firestore, getDocs, Query, QueryDocumentSnapshot, QuerySnapshot } from 'firebase/firestore'
const q: Query<any> = collection(db, 'videos')
const querySnapshot: QuerySnapshot<IVideoProcessed> = await getDocs(q)
const docs: QueryDocumentSnapshot<IVideoProcessed>[] = querySnapshot.docs
const videos: IVideoProcessed[] = docs.map((doc: QueryDocumentSnapshot<IVideoProcessed>) => doc.data())
where db has the type Firestore
You could get the whole collection as an object, rather than array like this:
async function getMarker() {
const snapshot = await firebase.firestore().collection('events').get()
const collection = {};
snapshot.forEach(doc => {
collection[doc.id] = doc.data();
});
return collection;
}
That would give you a better representation of what's in firestore. Nothing wrong with an array, just another option.
Two years late but I just began reading the Firestore documentation recently cover to cover for fun and found withConverter which I saw wasn't posted in any of the above answers. Thus:
If you want to include ids and also use withConverter (Firestore's version of ORMs, like ActiveRecord for Ruby on Rails, Entity Framework for .NET, etc), then this might be useful for you:
Somewhere in your project, you probably have your Event model properly defined. For example, something like:
Your model (in TypeScript):
./models/Event.js
export class Event {
constructor (
public id: string,
public title: string,
public datetime: Date
)
}
export const eventConverter = {
toFirestore: function (event: Event) {
return {
// id: event.id, // Note! Not in ".data()" of the model!
title: event.title,
datetime: event.datetime
}
},
fromFirestore: function (snapshot: any, options: any) {
const data = snapshot.data(options)
const id = snapshot.id
return new Event(id, data.title, data.datetime)
}
}
And then your client-side TypeScript code:
import { eventConverter } from './models/Event.js'
...
async function loadEvents () {
const qs = await firebase.firestore().collection('events')
.orderBy('datetime').limit(3) // Remember to limit return sizes!
.withConverter(eventConverter).get()
const events = qs.docs.map((doc: any) => doc.data())
...
}
Two interesting quirks of Firestore to notice (or at least, I thought were interesting):
Your event.id is actually stored "one-level-up" in snapshot.id and not snapshot.data().
If you're using TypeScript, the TS linter (or whatever it's called) sadly isn't smart enough to understand:
const events = qs.docs.map((doc: Event) => doc.data())
even though right above it you explicitly stated:
.withConverter(eventConverter)
Which is why it needs to be doc: any.
(But! You will actually get Array<Event> back! (Not Array<Map> back.) That's the entire point of withConverter... That way if you have any object methods (not shown here in this example), you can immediately use them.)
It makes sense to me but I guess I've gotten so greedy/spoiled that I just kinda expect my VS Code, ESLint, and the TS Watcher to literally do everything for me. πŸ˜‡ Oh well.
Formal docs (about withConverter and more) here: https://firebase.google.com/docs/firestore/query-data/get-data#custom_objects
I prefer to hide all code complexity in my services... so, I generally use something like this:
In my events.service.ts
async getEvents() {
const snapchot = await this.db.collection('events').ref.get();
return new Promise <Event[]> (resolve => {
const v = snapchot.docs.map(x => {
const obj = x.data();
obj.id = x.id;
return obj as Event;
});
resolve(v);
});
}
In my sth.page.ts
myList: Event[];
construct(private service: EventsService){}
async ngOnInit() {
this.myList = await this.service.getEvents();
}
Enjoy :)
Here's a simple version of the top answer, but going into an object with the document ids:
async getMarker() {
const snapshot = await firebase.firestore().collection('events').get()
return snapshot.docs.reduce(function (acc, doc, i) {
acc[doc.id] = doc.data();
return acc;
}, {});
}
General example to get products from Cloud Firestore:
Future<void> getAllProducts() async {
CollectionReference productsRef =
FirebaseFirestore.instance.collection('products');
final snapshot = await productsRef.get();
List<Map<String, dynamic>> map =
snapshot.docs.map((doc) => doc.data() as Map<String, dynamic>).toList();
}
In version 9 sdk of firebase you can get all the documents from a collection using following query:
const querySnapshot = await getDocs(collection(db, "cities"));
querySnapshot.docs.forEach((doc) => {
console.log(doc.id, " => ", doc.data());
});
See Get multiple documents from a collection
I understand your query, This is because how Javascript handles promises and variables. So basically events variable is hoisted with the value undefined and printed on the LOG 2 console log, while the Event Loop responsible for the promise call resulted in an array of objects as the value of the events variable and then the console log (LOG 1) was printed with the resolved promise response
All answers are true, but when you have heavy data you will face memory and bandwidth problems, so you have to write a [cursor] function to read data part by part.
also, you may face to Bandwidth Exhausted error, please have a look at this solution I have implemented on a gist
https://gist.github.com/navidshad/973e9c594a63838d1ebb8f2c2495cf87
Otherwise, you can use this cursor I written to read a collection doc by doc:
async function runCursor({
collection,
orderBy,
limit = 1000,
onDoc,
onDone,
}) {
let lastDoc;
let allowGoAhead = true;
const getDocs = () => {
let query = admin.firestore().collection(collection).orderBy(orderBy).limit(limit)
// Start from last part
if (lastDoc) query = query.startAfter(lastDoc)
return query.get().then(sp => {
if (sp.docs.length > 0) {
for (let i = 0; i < sp.docs.length; i++) {
const doc = sp.docs[i];
if (onDoc) onDoc(doc);
}
// define end of this part
lastDoc = sp.docs[sp.docs.length - 1]
// continue the cursor
allowGoAhead = true
} else {
// stop cursor if there is not more docs
allowGoAhead = false;
}
}).catch(error => {
console.log(error);
})
}
// Read part by part
while (allowGoAhead) {
await getDocs();
}
onDone();
}

Categories

Resources