Having issues with Await not waiting using async/await - javascript

I have a vue.js project and am using Vuex for my store. I am trying to process all notifications to users within the store and I am having some issues with async/await.
I am sure its something very simple and trivial but I am stuck. Any help is much appreciated.
Here is my function
async getNotifications() {
console.log('1')
const internalNotifications = await this.$store.getters['AppData/getInternalNotifications']
console.log('2')
if(internalNotifications) {
this.notifications = internalNotifications
this.message = true
console.log('4 ', internalNotifications)
}
}
Here is my function in the store to get the notifications and dispatch them.
async getInternalNotifications({ dispatch }, { cid, user, roles, isSupperAdmin }) {
console.log('getInternalNotifications')
let internalNotifications = []
// Get all the notifications for this church
let getAllNotifications = await db
.collection('notifications')
.where('cid', '==', cid)
.where('active', '==', true)
.orderBy('created')
.get()
for (const notificationDoc of getAllNotifications.docs) {
let notification = notificationDoc.data()
notification.id = notificationDoc.id
// check to make sure this notification has not already been read
let getAllReadNotifications = await db
.collection('notificationsread')
.where('notificationid', '==', notification.id)
.where('userid', '==', user.uid)
.get()
if (getAllReadNotifications.empty)
internalNotifications.push(notification)
}
if (!isSupperAdmin && internalNotifications.length > 0) {
const hasAudience = internalNotifications.filter((el) => {
return roles.some(r => el.audience.includes(r))
})
hasAudience.sort((a, b) => (a.created < b.created) ? 1 : -1)
internalNotifications = hasAudience[0]
}
console.log('3 ', internalNotifications)
dispatch('addInternalNotification', internalNotifications)
},
My thinking is when viewing the console log I should see the logs in order 1,3,2,4 but instead I get 1,2,4,3 and as you can see from the screen shot it's an Observer not the actual array/object.
see screen shot of console log

What appears to be happening, and looking at the order of logs, is that this.$store.getters does not return a Promise and therefore adding await does nothing.
Make sure that this.$store.getters is actually returning a Promise that can be awaited or look at using Actions. From the docs if you wanted to use an action for this, you could try something like the following:
NOTE All of this is untested and I don't know vue, so use with caution knowing changes may be needed and best practices may not be getting followed by this approach.
actions: {
async getInternalNotifications({ dispatch }) {
return new Promise((resolve, reject) => {
console.log('getInternalNotifications')
let internalNotifications = []
// Get all the notifications for this church
let getAllNotifications = await db
.collection('notifications')
.where('cid', '==', cid)
.where('active', '==', true)
.orderBy('created')
.get()
for (const notificationDoc of getAllNotifications.docs) {
let notification = notificationDoc.data()
notification.id = notificationDoc.id
// check to make sure this notification has not already been read
let getAllReadNotifications = await db
.collection('notificationsread')
.where('notificationid', '==', notification.id)
.where('userid', '==', user.uid)
.get()
if (getAllReadNotifications.empty)
internalNotifications.push(notification)
}
if (!isSupperAdmin && internalNotifications.length > 0) {
const hasAudience = internalNotifications.filter((el) => {
return roles.some(r => el.audience.includes(r))
})
hasAudience.sort((a, b) => (a.created < b.created) ? 1 : -1)
internalNotifications = hasAudience[0]
}
console.log('3 ', internalNotifications)
dispatch('addInternalNotification', internalNotifications)
resolve(internalNotifications)
// TODO: Reject error scenarios, e.g. wrap you db calls in a try...catch and reject the promise with the error
})
}
async getNotifications() {
console.log('1')
const internalNotifications = await this.$store.dispatch('AppData/getInternalNotifications')
console.log('2')
if(internalNotifications) {
this.notifications = internalNotifications
this.message = true
console.log('4 ', internalNotifications)
}
}

I was working on a web app recently and I think I can help. Someone correct me if I'm wrong, but typically after calling an async function, I follow it with a .then. Example:
const myPromise = anAsyncFunction() // should return a promise
myPromise.then (
function(value) {
// function to be executed when the async function is finished
},
function(error) {
// error handling
}
)
Otherwise, my app would not wait for the async function to finish and would not have the right variable in time to process it.

Related

Concurrent HTTP requests with limit in pool and with exceptions

I have been looking for the perfect library that does async requests in batched pools. I have looked at p-limit, tiny-async-pool and others but they all have the same limitation for what I am trying to do, namely Promise.all fails if any request fails.
What I have found in several projects, is that sometimes doing many requests to servers, they respond with a 500, normally this is because is a proxy server, having this failure is expected and can be safely retried. So I would like to capture a given error, throw it with a custom error, I can then pick up on the async pool function, and use Promise.allSettled to collect the errors and run them recursively.
So I have gone into tiny-async-pool source code and modified it so it does just that,
This is what I have done:
const data = [
'https://apple.com',
'https://ibm.com',
'https://microsoft.com',
'https://amazon.com',
'https://apple.com',
'https://sony.com',
'https://hp.com',
'https://dell.com',
'https://compaq.com',
'https://apple.com'
]
// Concurrency Limit
const poolLimit = 3
// Final array
const processedData = []
// Mock API response promise
const API = (item) => {
return new Promise((resolve, reject) => {
if (item === 'https://apple.com') reject(`${item}/repeat`)
setTimeout(
() => resolve(item.replace(/^https?:\/\//, '')),
Math.round(Math.random() * 2000) + 4000
)
})
}
// Iterator Function - It throws the error asyncPool will pickup as timeout exception
const fetchAPI = async (item) => {
try {
processedData.push(await API(item))
} catch (error) {
throw { name: 'timeout', error }
}
}
// Async pool. 'Recursive' modification of tiny-async-pool
async function asyncPool(poolLimit, array, iteratorFn, exception) {
const promises = []
const queue = []
for (const [index, item] of array.entries()) {
console.log(`\rProcessing ${index + 1}/${array.length} ...`)
// Iterator is always a promise from the API
const pro = iteratorFn(item, array)
promises.push(pro)
// If concurrent limit exceeded
if (poolLimit <= array.length) {
// Is it removing the promise after resolving it?
const racer = pro
.then(() => queue.splice(queue.indexOf(racer), 1))
// If I do not catch I get uncaught exeption
.catch(() => queue.splice(queue.indexOf(racer), 1))
queue.push(racer)
if (queue.length >= poolLimit) await Promise.race(queue)
}
}
// Collect promises results
const results = await Promise.allSettled(promises)
// Make array with exception errors
const rejected = results
.filter(({ status, reason }) => status === 'rejected' && reason.name === exception)
.map(({ reason }) => reason.error)
// If we have any errors run recursively
if (rejected.length) {
await asyncPool(poolLimit, rejected, iteratorFn, exception)
}
}
// Start pool request
;(async () => {
await asyncPool(poolLimit, data, fetchAPI, 'timeout')
console.log(processedData)
})()
I am fascinated by what tiny-async-pool does, but there is this part that even thou I know what is doing I cannot explain how:
const racer = pro.then(() => queue.splice(queue.indexOf(racer), 1))
And so because I am throwing an error on a condition, I need to add a catch before the Promise.race() runs.
const racer = pro
.then(() => queue.splice(queue.indexOf(racer), 1))
.catch(() => queue.splice(queue.indexOf(racer), 1))
it works, but again I cannot really explain why.
Could anyone shed some light on this?
Thanks.

Cannot resolve Promise in Node.js App with chrome-cookies-secure module

I'm working on a local Node.js app that needs to access the Google Chrome cookies. I've found the chrome-cookies-secure library that seems to do the job but I just can't figure out what's wrong with the code below.
const chrome = require('chrome-cookies-secure');
const domains = [
"google.com"
];
const resolveCookies = async () => {
let result = [];
for(domain of domains) {
await chrome.getCookies(`https://${domain}/`, (err, cookies) => {
result.push(cookies);
// console.log(cookies); //? This is going to correctly print the results
})
}
return result;
}
const final = resolveCookies();
console.log(final); //! This is going to return a Promise { <pending> } object
The idea is that I just want to store the cookies from all the domains in a list but no matter what I cannot resolve the Promise.
I didn't see any examples with the async call for this module but if I don't use it it's going to return me an empty list after the script execution.
My Node Version: v14.4.0
What am I doing wrong?
It looks like the implementation of getCookies is not correctly awaiting the asynchronous processes. You can see in the implementation that although getCookies itself is async, it calls getDerivedKey without awaiting it (and that function isn't async anyway).
Rather than trying to rely on this implementation, I'd suggest using Util.promisify to create a proper promise via the callback:
const util = require('util');
const chrome = require('chrome-cookies-secure');
const getCookies = util.promisify(chrome.getCookies);
// ...
const cookies = await getCookies(`https://${domain}/`);
Note that, as Reece Daniels pointed out in the comments, the getCookies implementation actually takes a profile parameter after the callback; if you need to use that parameter, you can't use the built-in promisify. You'd have to wrap it yourself instead, this could look like e.g.:
const getCookies = (url, format, profile) => new Promise((resolve, reject) => {
chrome.getCookies(url, format, (err, cookies) => {
if (err) {
reject(err);
} else {
resolve(cookies);
}
}, profile);
});
They already tried to fix the promise upstream, but the PR hasn't been merged in nearly nine months.
Note that once you have a working function to call you can also convert:
const resolveCookies = async () => {
let result = [];
for(domain of domains) {
await chrome.getCookies(`https://${domain}/`, (err, cookies) => {
result.push(cookies);
// console.log(cookies); //? This is going to correctly print the results
})
}
return result;
}
to simply:
const resolveCookies = () => Promise.all(domains.map((domain) => getCookies(`https://${domain}/`)));
An async function returns a Promise.
So your resolveCookies function will also return a Promise as you noticed.
You need to either chain the console.log with a .then e.g.
resolveCookies().then(console.log);
Or if you need to set it to a variable like final you need to await that Promise too. In that case you need an async IIFE:
(async () => {
const final = await resolveCookies();
console.log(final);
})();
try this.
const chrome = require('chrome-cookies-secure');
const domains = [
"www.google.com"
];
const resolveCookies = async() => {
let result = [];
for (domain of domains) {
const cookies = await getCookies(domain)
result.push(cookies)
}
return Promise.resolve(result);
}
const getCookies = async (domain) => {
chrome.getCookies(`https://${domain}/`, (err, cookies) => {
return Promise.resolve(cookies);
})
}
resolveCookies().then((resp) => {
console.log('FINAL ',resp)
}).catch((e) => {
console.log('ERROR ', e)
})

Get one document from Firestore and return it to a variable

I have less experience with NodeJS, but I am trying to obtain one User document from Firestore.
const fs = firebase.firestore();
const usersRef = fs.collection('users');
let findUserByContact = (contact) => {
let res = usersRef.where('contact', '==', contact).get().then(querySnapshot => {
if (!querySnapshot.empty) {
return querySnapshot.docs[0].data();
} else {
return false;
}
});
};
I am trying to return the first document thats found. However that will always returns undefined when I am calling findUserByContact.
I could use console.log to see the output of my query. But I can't manage to assign it to a variable.
I've read into Promises, and I saw that it has to resolve something. I was trying to call resolve with the data I wanted to return but that gave me an error as wel.
You need to return the promise chain, as follows:
let findUserByContact = (contact) => {
return usersRef.where('contact', '==', contact).get()
.then(querySnapshot => {
if (!querySnapshot.empty) {
return querySnapshot.docs[0].data();
} else {
return false;
}
});
};
Note that the answer of #AkashDathan is also totally valid and the use of async/await makes it easier to read.
I would recommend that you use the async/await syntax
let findUserByContact = async (contact) => {
let querySnapshot = await usersRef.where('contact', '==', contact).get();
if (querySnapshot.empty) return false;
return querySnapshot.docs[0].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.

Firebase: Transaction with async/await

I'm trying to use async/await with transaction.
But getting error "Argument "updateFunction" is not a valid function."
var docRef = admin.firestore().collection("docs").doc(docId);
let transaction = admin.firestore().runTransaction();
let doc = await transaction.get(docRef);
if (!doc.exists) {throw ("doc not found");}
var newLikes = doc.data().likes + 1;
await transaction.update(docRef, { likes: newLikes });
The above did not work for me and resulted in this error: "[Error: Every document read in a transaction must also be written.]".
The below code makes use of async/await and works fine.
try{
await db.runTransaction(async transaction => {
const doc = await transaction.get(ref);
if(!doc.exists){
throw "Document does not exist";
}
const newCount = doc.data().count + 1;
transaction.update(ref, {
count: newCount,
});
})
} catch(e){
console.log('transaction failed', e);
}
If you look at the docs you see that the function passed to runTransaction is a function returning a promise (the result of transaction.get().then()). Since an async function is just a function returning a promise you might as well write db.runTransaction(async transaction => {})
You only need to return something from this function if you want to pass data out of the transaction. For example if you only perform updates you won't return anything. Also note that the update function returns the transaction itself so you can chain them:
try {
await db.runTransaction(async transaction => {
transaction
.update(
db.collection("col1").doc(id1),
dataFor1
)
.update(
db.collection("col2").doc(id2),
dataFor2
);
});
} catch (err) {
throw new Error(`Failed transaction: ${err.message}`);
}
IMPORTANT: As noted by a couple of the users, this solution doesn't use the transaction properly. It just gets the doc using a transaction, but the update runs outside of it.
Check alsky's answer. https://stackoverflow.com/a/52452831/683157
Take a look to the documentation, runTransaction must receive the updateFunction function as parameter. (https://firebase.google.com/docs/reference/js/firebase.firestore.Firestore#runTransaction)
Try this
var docRef = admin.firestore().collection("docs").doc(docId);
let doc = await admin.firestore().runTransaction(t => t.get(docRef));
if (!doc.exists) {throw ("doc not found");}
var newLikes = doc.data().likes + 1;
await doc.ref.update({ likes: newLikes });
In my case, the only way I could get to run my transaction was:
const firestore = admin.firestore();
const txRes = await firestore.runTransaction(async (tx) => {
const docRef = await tx.get( firestore.collection('posts').doc( context.params.postId ) );
if(!docRef.exists) {
throw new Error('Error - onWrite: docRef does not exist');
}
const totalComments = docRef.data().comments + 1;
return tx.update(docRef.ref, { comments: totalComments }, {});
});
I needed to add my 'collection().doc()' to tx.get directly and when calling tx.update, I needed to apply 'docRef.ref', without '.ref' was not working...

Categories