Returned Function becomes a Promise and can no longer be called - javascript

I have this function:
async listenProgrammStatus(uid, programmId, onUpdate) {
const unsubscribe = firebase.firestore().collection('Users').doc(uid).collection('ProgrammStatus').doc(uid + programmId).onSnapshot(function (doc) {
console.log('DOC', doc);
if (doc.exists) {
const status = doc.data();
status.id = doc.id;
onUpdate(status);
}
})
console.log(unsubscribe)
return unsubscribe;
},
And I call it like this:
const unsubscribeStatus = db.listenProgrammStatus(this.user.uid, this.programm.id, (status) => {
console.log('STATUS', status);
this.status = status;
})
console.log('UNSUB', unsubscribeStatus)
this.unsubscribeStatus = unsubscribeStatus;
How ever the unsubscribe function I returned doesn't seem to work after I returned it. calling unsubscribeStatus() won't work.
unsubscribe() works in listenProgrammStatus but when I return the function, it seems to become a Promise and I can't call unsubscribeStatus ().
Any Ideas?

The async keyword has two effects:
It makes the function return a Promise that resolves as whatever value you use with the return keyword
It allows you to use await inside the function
If you don't want a promise, don't mark the function as async. You aren't using await in it anyway!

Related

Using useEffect with async?

I'm using this code:
useFocusEffect(
useCallback(async () => {
const user = JSON.parse(await AsyncStorage.getItem("user"));
if (user.uid) {
const dbRef = ref(dbDatabase, "/activity/" + user.uid);
onValue(query(dbRef, limitToLast(20)), (snapshot) => {
console.log(snapshot.val());
});
return () => {
off(dbRef);
};
}
}, [])
);
I'm getting this error:
An effect function must not return anything besides a function, which
is used for clean-up. It looks like you wrote 'useFocusEffect(async ()
=> ...)' or returned a Promise. Instead, write the async function inside your effect and call it immediately.
I tried to put everything inside an async function, but then the off() is not being called.
Define the dbRef variable outside the nested async function so your cleanup callback can reference it, and allow for the possibility it may not be set as of when the cleanup occurs.
Also, whenever using an async function in a place that doesn't handle the promise the function returns, ensure you don't allow the function to throw an error (return a rejected promise), since nothing will handle that rejected promise.
Also, since the component could be unmounted during the await, you need to be sure that the async function doesn't continue its logic when we know the cleanup won't happen (because it already happened), so you may want a flag for that (didCleanup in the below).
So something like this:
useFocusEffect(
useCallback(() => {
let dbRef;
let didCleanup = false;
(async() => {
try {
const user = JSON.parse(await AsyncStorage.getItem("user"));
if (!didCleanup && user.uid) {
dbRef = ref(dbDatabase, "/activity/" + user.uid);
onValue(query(dbRef, limitToLast(20)), (snapshot) => {
console.log(snapshot.val());
});
}
} catch (error) {
// ...handle/report the error...
}
})();
return () => {
didCleanup = true;
if (dbRef) {
off(dbRef);
}
};
}, [])
);

How to get the return value of a async function that returns a promise

So I have a code like this
const getAllProduct = async () => {
let allProduct = "";
let config = {
method: "get",
url: db_base_url + "/products/",
headers: {
Authorization: "Bearer " + token.access.token,
"Content-Type": "application/json",
},
};
try {
let response = await axios(config);
allProduct = response.data.results;
} catch (error) {
console.log(error);
}
console.log(allProduct);
return allProduct;
};
The console.log(allProduct) do prints an array.
The function will be called on the render method of react by
return (<div> {getAllProduct()} </div>)
I've tried to do
return (<div> {console.log(getAllProduct())} </div>
But the console.log on rendering returns to be Promise Object instead of the results array.
How can I go around this?
async functions return a Promise which means their result is not immediately available.
What you need to do is either await the result of calling getAllProduct() function or chain a then() method call.
Looking at your code, i assume that you want to call getAllProduct() function after after your component is rendered. If that's the case, useEffect() hook is where you should call your function.
You could define and call your function inside the useEffect() hook and once the data is available, save that in the local state your component.
First define the local state of the component
const [products, setProducts] = useState([]);
Define and call the getAllProduct() function inside the useEffect() hook.
useEffect(() => {
const getAllProduct = async () => {
...
try {
let response = await axios(config);
allProduct = response.data.results;
// save the data in the state
setProducts(allProduct);
} catch (error) {
console.log(error);
}
};
// call your async function
getAllProduct();
}, []);
Finally, inside the JSX, .map() over the products array and render the products in whatever way you want to render in the DOM.
return (
<div>
{ products.map(prod => {
// return some JSX with the appropriate data
}) }
</div>
);
use
getAllProduct().then(res => console.log(res))
async function always return a promise you use await before call it getAllProduct()
const res = await getAllProduct();
console.log(res)
In my case daisy chaining .then didn't work. Possibly due to fact that I had a helper JS file that held all DB related functions and their data was utilized across various React components.
What did work was daisy chaining await within an async. I modified code where it works for same Component (like in your case). But we can take same logic , put async function in different JS file and use its response in some other component.
Disclaimer : I haven't tested below code as my case was different.
useEffect( () => {
var handleError = function (err) {
console.warn(err);
return new Response(JSON.stringify({
code: 400,
message: 'Error in axios query execution'
}));
};
const getAllProduct = async () => {
let allProduct = "";
...
const response = await ( axios(config).catch(handleError));
allProduct = await response;
return allProduct;
}
},[]);
// Then inside JSX return
getAllProduct().then( data => {
// Make use of data
});

Unable to return a value from an async function using firestore

I am new to async and am trying to return a value from a Firestore db using node.
The code does not produce any errors, nor does it produce any results!
I want to read the db, get the first match and return this to the var country.
const {Firestore} = require('#google-cloud/firestore');
const db = new Firestore();
async function getCountry() {
let collectionRef = db.collection('groups');
collectionRef.where('name', '==', 'Australia').get()
.then(snapshot => {
if (snapshot.empty) {
console.log('No matching documents.');
return "Hello World";
}
const docRef = snapshot.docs[0];
return docRef;
})
.catch(err => {
console.log('Error getting documents', err);
});
}
let country = getCountry();
When you declare an function async, that means it always returns a promise. It's generally expected that the code inside it will use await to deal with other promises generated within that function. The final returned promise will resolve with the value returned by the function.
First of all, your async function should look more like this:
async function getCountry() {
let collectionRef = db.collection('groups');
const snapshot = await collectionRef.where('name', '==', 'Australia').get()
if (snapshot.empty) {
console.log('No matching documents.');
// you might want to reconsider this value
return "Hello World";
}
else {
return snapshot.docs[0];
})
}
Since it returns a promise, you would invoke it like any other function that returns a promise:
try {
let country = await getCountry();
}
catch (error) {
console.error(...)
}
If you can't use await in the context of your call to getCountry(), you will have to handle it normally:
getCountry()
.then(country => {
console.log(country);
})
.catch(error => {
console.error(...)
})
The moment you sign up to use async/await instead of then/catch, things become much different. I suggest reading up more on how it works.

Waiting for async function to return true or false - how do I check return value?

I have an async function songAlreadyInQueue that will return true if an ID is found in a database, false otherwise. I want to use the function like such:
if(!songAlreadyInQueue(ref, id)){
// do stuff
}
But I know that since it's an async function, I have to wait for the result to come back before I truly know if it's true or false. What I have above is always false so I tried this:
songAlreadyInQueue(ref, id).then((wasFound) => {
console.log("wasfound = " + wasFound)
if(!wasFound){
//do stuff
}
})
But that doesn't work either. What is the proper way to wait for this async function to finish? This is what my async function looks like (simplified):
async function songAlreadyInQueue(requestQueueRef, requestID) {
var querye = requestQueueRef.where("id", "==", requestID)
.get()
.then((snap) => {
snap.docs.forEach(doc => {
if (doc.exists) {
console.log("**************FOUND REQUEST IN QUEUE ALREADY!")
return true
}
})
return false
})
return querye // not sure if correct. Is this "returning a promise"?
}
With an async function you should use await instead of then() so you're not creating another anonymous function. You are returning from an anonymous function rather than the async function. try this.
async function songAlreadyInQueue(requestQueueRef, requestID) {
var snap = await requestQueueRef.where("id", "==", requestID).get()
var found = false;
snap.docs.forEach(doc => {
if (doc.exists) {
console.log("**************FOUND REQUEST IN QUEUE ALREADY!")
found = true
}
})
return found
}

What's the promise chaining equivalent of awaiting multiple async functions?

I'm studying the usage of promsies and async/await.
I've wrote the following code, which does the following:
It gets some database's data (using Knex.js),
Handles that data,
Assigns the handled data into a specified property.
These 3 steps are done multiple times (In the following code, it's done twice), and are always awaited:
async function run() {
return await getData();
}
async function getData() {
let handledData = {};
handledData.res1 = await knex.select('column1').from('table1').where('column1', '1')
.then(data => handleData(data))
.catch(handleError);
handledData.res2 = await knex.select('column1').from('table1').where('column1', '2')
.then(data => handleData(data, handledData))
.catch(handleError);
return handledData;
}
async function handleData(data) {
let res = [];
data.forEach(item => {
res.push(item.column1);
});
return res;
}
function handleError (error) {
console.log(error);
}
Now, I'm trying to write the promise-chaining equivalent of getData, and this is what I came up with:
async function getData() {
let handledData = {};
let promise = new Promise(function(resolve, error){ resolve(); });
promise
.then(function () {
return knex.select('column1').from('table1').where('column1', '1')
.then(data => handleData(data))
.catch(handleError);
})
.then(function(handled){
handledData.res1 = handled;
return knex.select('column1').from('table1').where('column1', '2')
.then(data => handleData(data))
.catch(handleError);
})
.then(function(handled){
handledData.res2 = handled;
return handledData;
})
.catch(handleError);
return promise;
}
But this doesn't quite work. What happens is that after the first then returns, the await inside run ends its awaiting, which causes run to return - and only then the second then is executed.
How can I make the promise-chaining version work as the multiple-await version does?
(and please, feel free to point out any misunderstaings I made of promises/async-await)
If possible, I'd recommend using Promise.all instead, it'll make your script run faster in addition to making the logic clearer:
const getData = Promise.all([
knex.select('column1').from('table1').where('column1', '1')
// Simply pass the function name as a parameter to the `.then`:
.then(handleData)
.catch(handleError),
knex.select('column1').from('table1').where('column1', '2')
.then(handleData)
.catch(handleError)
])
.then(([res1, res1]) => ({ res1, res2 }));
knex.select().then() returns a promise, so you don't need to wrap it in another promise you just need to set up the chain of then()s and return the whole thing. The result will be that getData returns the promise from the last then. You can return the value you want from that then() which will make it available to the caller. For example:
function run() {
getData()
.then(handledData => console.log(handledData) /* do something with data */)
}
function getData() {
let handledData = {};
// need to return this promise to callers can access it
return knex.select('column1').from('table1').where('column1', '1')
.then(data => handledData.res1 = handleData(data))
.then(() => knex.select('column1').from('table1').where('column1', '2'))
.then(data => {
handledData.res2 = handleData(data)
return handledData
})
.catch(handleError);
}
You could also set this up to pass the handledData object thought the chain, but you don't need to in this case.
The function handleData() is synchronous, so you don't need to make it an async function.

Categories