async function has old state React-Redux - javascript

I am working on a React project where user can upload and remove photos in projects. After Uploading new image it should be visible to user only if corresponding projectis selected. The solution is fairly simple to check
if
(selectedProject.projectID=== imageMetadata.projectID)
where
selectedProject.projectID: Id of currently selected project( Comming from
Redux Store )
imageMetadata.projectID: Id of project to which uploaded image belongs.
All of this is being done inside an async function and the problem we are facing is that even after selectedAlbum.albumID is changed everywhere else, this function still has its old value. Is it because the function is async?
This is my code:
let projectId = selectedProject.projectID;
const responses = await Promise.all(
filesToUpload.map( async (file) => {
let res = await imageStoreApiHandler.uploadPhoto(file, projectId);
notifyUploadProgress(count++, total, notifcationKey);
if (res.status === 200) {
let imageMetadata: any = res.data[0];
if (selectedProject.projectID === imageMetadata.projectID) {
addImage(imageMetadata);
}
}
return res;
}));

It's probably a closure problem where the uploadhandler keeps caching the first function.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures
Maybe write a function which will return a promise all array based on the parameters current project id.

Related

Perform fetch request within a Firestore transaction: receiving "Cannot modify a WriteBatch that has been committed"

I'm trying to perform a fetch request within a transaction but when the code executes I receive the following error.
Error: Cannot modify a WriteBatch that has been committed.
The steps the function is performing are the following:
Compute document references (taken from an external source)
Query the documents available in Firestore
Verify if document exists
Fetch for further details (lazy loading mechanism)
Start populating first level collection
Start populating second level collection
Below the code I'm using.
await firestore.runTransaction(async (transaction) => {
// 1. Compute document references
const docRefs = computeDocRefs(colName, itemsDict);
// 2. Query the documents available in Firestore
const snapshots = await transaction.getAll(...docRefs);
snapshots.forEach(async (snapshot) => {
// 3. Verify if document exists
if (!snapshot.exists) {
console.log(snapshot.id + " does not exists");
const item = itemsDict[snapshot.id];
if (item) {
// 4. Fetch for further details
const response = await fetchData(item.detailUrl);
const detailItemsDict = prepareDetailPageData(response);
// 5. Start populating first level collection
transaction.set(snapshot.ref, {
index: item.index,
detailUrl: item.detailUrl,
title: item.title,
});
// 6. Start populating second level collection
const subColRef = colRef.doc(snapshot.id).collection(subColName);
detailItemsDict.detailItems.forEach((detailItem) => {
const subColDocRef = subColRef.doc();
transaction.set(subColDocRef, {
title: detailItem.title,
pdfUrl: detailItem.pdfUrl,
});
});
}
} else {
console.log(snapshot.id + " exists");
}
});
});
computeDocRefs is described below
function computeDocRefs(colName, itemsDict) {
const identifiers = Object.keys(itemsDict);
const docRefs = identifiers.map((identifier) => {
const docId = `${colName}/${identifier}`
return firestore.doc(docId);
});
return docRefs;
}
while fetchData uses axios under the hood
async function fetchData(url) {
const response = await axios(url);
if (response.status !== 200) {
throw new Error('Fetched data failed!');
}
return response;
}
prepareMainPageData and prepareDetailPageData are functions that prepare the data normalizing them.
If I comment the await fetchData(item.detailUrl), the first level collection with all the documents associated to it are stored correctly.
On the contrary with await fetchData(item.detailUrl) the errors happens below the following comment: // 5. Start populating first level collection.
The order of the operation are important since I do now want to make the second call if not necessary.
Are you able to guide me towards the correct solution?
The problem is due to the fact that forEach and async/await do not work well together. For example: Using async/await with a forEach loop.
Now I've completely changed the approach I'm following and now it works smoothly.
The code now is like the following:
// Read transaction to retrieve the items that are not yet available in Firestore
const itemsToFetch = await readItemsToFetch(itemsDict, colName);
// Merge the items previously retrieved to grab additional details through fetch network calls
const fetchedItems = await aggregateItemsToFetch(itemsToFetch);
// Write transaction (Batched Write) to save items into Firestore
const result = await writeFetchedItems(fetchedItems, colName, subColName);
A big thanks goes to Doug Stevenson and Renaud Tarnec.

Google cloud function onCreate not writing to the Database

I have created a google function for firebase that when A new conversation is added the function attaches it to the Users table under a new collection for each user in the conversation but when the function gets triggered nothing happens in the database, So far I have console logged the values tp make sure they ware being set right and they ware I have also tried looking at the google function logs and there are no errors according to the logs the script ran with no errors
Here is the code for the function
import * as functions from 'firebase-functions';
import * as admin from 'firebase-admin';
admin.initializeApp();
export const onConversationCreated = functions.firestore.document("Conversations/{conversationID}").onCreate((snapshot, context) => {
let data = snapshot.data();
let conversationID = context.params.conversationID;
if(data){
let members = data.members;
for(let index = 0; index < members.length; index++){
let currentUserID = members[index];
let remainingUserIDs = members.filter((u: string) => u !== currentUserID)
remainingUserIDs.forEach((m: string) => {
return admin.firestore().collection("Users").doc(m).get().then((_doc) => {
let userData = _doc.data();
if(userData){
return admin.firestore().collection("Users").doc(currentUserID).collection("Conversations").doc(m).create({
"conversationID": conversationID,
"image": userData.image,
"unseenCount": 1,
});
}
return null;
}).catch(() => {return null})
});
}
}
return null;
});
Can someone tell me if there is something wrong with my code or do I have to give functions permission to write to the cloud firestore database?
You are not dealing with promises correctly. Your function needs to return a promise that resolves with all of the asynchronous work is complete. Right now, it's just returning null, and not waiting for any work to complete.
All Firestore operations are asynchronous and return a promise. You will need to use these promises to build a single promise to return from the function. That means each time you query and write a document, that's generating another promise to handle.
Also, you should know that there is no method create() that you're trying to use to add a document. Perhaps you meant to use set() instead. The code will crash if it tries to execute create().

Extracting data from an object inside of another function without writing to JSON?

I'm working on writing a Discord bot with music functionality using discord.js and node, along with a handful of other packages like yt-search and ytdl-core.
The problem I'm trying to solve is related to the code below (newVar was just a placeholder while testing):
let regex = /^https/i;
let isUrl = regex.test(checkUrl);
let songInfo;
if (!isUrl) {
yts(suffix, function (err, r) {
if(err) console.error(err);
const videos = r.videos;
let data = JSON.stringify(videos[0])
fs.writeFileSync('youtube.json', data)
})
let newVar = require('../youtube.json');
let {url, title} = newVar;
songInfo = await ytdl.getInfo(newVar.url)
} else {
songInfo = await ytdl.getInfo(args[1]);
}
const song = {
title: songInfo.title,
url: songInfo.video_url,
};
What I'm trying to do,
Is to check whether or not the 'suffix' is a URL, and if not, run suffix through the yts() (yt-search) function, and get the URL from the returned object.
Then pass that url value through the ytdl.getInfo() function.
It works as intended to an extent, but writing to the JSON is causing a problem in that it is returning the same URL even when a new search is completed, until the program is restarted,
Then it will repeat the process with whatever value was stored in the JSON file when the program was executed. However, I get the results when I console.log(videos[0].url), and the value changes with each query, but I have no way to pass that data outside of the yts() function without writing to the JSON first.
Any ideas?
I'm sorry if I'm not specific enough, or confused in my understanding, this is one of my first "complex" projects. It could also be that the issue exists elsewhere in the module, but from what I've done so far I think it's somewhere in the code shown above. Thanks!
here is something you can do to get it right.
const getSongInfo = (url, suffix) => {
return new Promise(async (resolve, reject) => {
let regex = /^https/i;
let isUrl = regex.test(url);
if (!isUrl) {
// netween where is the suffix variable ?
yts(suffix, async (err, r) => {
if(err) reject(err);
const videos = r.videos;
let data = JSON.stringify(videos[0]);
// still don't know why bother save it and access it again.
fs.writeFileSync('youtube.json', data);
let newVar = require('../youtube.json');
resolve(await ytdl.getInfo(newVar.url));
});
} else {
resolve(await ytdl.getInfo(args[1]));
}
});
}
// hope the outer function is async
let songInfo = await getSongInfo(checkUrl, suffix);
const song = {
title: songInfo.title,
url: songInfo.video_url,
};
Between make sure to check that suffix variable which is not in scope.

Wait until async is completed (await)

I am trying to get my head around with async/await
I am using https://github.com/sunnylqm/react-native-storage for my project in react-native.
I am using async storage to store few critical information such as selected user locale, I need to retrieve this value before screen is rendered to display screen based on selected locale.
I have tried implementing several helper functions, it works with callback, what I need is to return the value instead of callback and wait until the value is fetched. Below are few examples I tried.
// 1
_selectedLocale = async () => {
try {
const value = await global.storage.load({key: 'selectedLocale'});
return value
} catch (error) {
console.log(value)
}
}
var selectedLocale = _selectedLocale();
// 2
export async function _selectedLocale() {
return storage.load({key: 'selectedLocale'});
}
var selectedLocale = _selectedLocale();
// 3
export function selectedLocale(callback) {
storage.load({key: 'selectedLocale'}).catch(e => {
callback(RNLanguages.language);
}).then(function(locale) {
callback(locale);
});
}
This is not working, I am looking to
Implement a helper function to retrieve a value based on key
Wait until the value is retrieved (sync)
Can someone point me to right direction
Thanks
UPDATE1:
Here is how I am using the callback
selectedLocale(function(locale) {
global.layoutIsRTL = 'ar' == locale;
ReactNative.I18nManager.allowRTL(true);
global.i18n.locale = locale
});
UPDATE2:
It seems someone has already done this, here is the reference https://github.com/sunnylqm/react-native-storage/issues/206 unfortunately I am having hard time understanding it.
async implicitly returns a promise, so you need to await the function call as well if you want the value there:
var selectedLocale = await _selectedLocale();
but you can only do that inside of another async function

Async Function in AWS Lambda Alexa Intent with Yummly API

I'm trying to retrieve data from the Yummly API through Amazon Alexa using ws-yummly in Node.js deployed on AWS Lambda. I'm fairly new to all aspects of this, but new to javascript in particular (Python is my 'native' language).
Here is what I have for my recommendation intent:
"RecommendationIntent": function () {
// delegate to Alexa to collect all the required slots
let filledSlots = delegateSlotCollection.call(this);
if (!filledSlots) {
return;
}
console.log("filled slots: " + JSON.stringify(filledSlots));
// at this point, we know that all required slots are filled.
let slotValues = getSlotValues(filledSlots);
console.log(JSON.stringify(slotValues));
const mainIngredientQuery = slotValues.mainIngredient.resolved;
async function main (queryWord) {
const resp = await Yummly.query(queryWord)
.maxTotalTimeInSeconds(1400)
.maxResults(20)
.minRating(3)
.get();
const names = resp.matches.map(recipe => recipe.recipeName);
const speechOutput = String(names[0]);
this.response.speak(speechOutput);
this.emit(":responseReady");
}
main(mainIngredientQuery).catch(error => console.error(error))
},
This is in the index.js file deployed on AWS lambda. I have isolated the problem to the async function. I have tested the function locally and it returns to console.log a list of recipe names. I want to have Alexa say these names. Or at least one name.
If I put the speechOutput assignment inside (as it is now), then I get an error that the 'Speechlet Response is set to null'.
If I tell it to 'return names' and set the external assignment to names or names[0] I get object promise or undefined (respectively).
Everything else in my program works fine and test these two bits apart they work, but putting them together doesn't work. I think that this is a syntax or placement error, but I don't understand the structure or formatting well enough yet (still learning) to understand what to try next.
How about using Promise.then like this:
async function main (queryWord) {
const resp = await Yummly.query(queryWord)
.maxTotalTimeInSeconds(1400)
.maxResults(20)
.minRating(3)
.get();
const names = resp.matches.map(recipe => recipe.recipeName);
return String(names[0]);
}
main(mainIngredientQuery)
.catch( error => console.error(error) )
.then( data => {
this.response.speak( data );
this.emit(":responseReady");
});
I'm updating this in case anyone else has the same problem.
If you notice, in my original code, I had an async function inside the intent. That didnt work because the intent itself was/is a function. By making the intent function an async function instead, I was able to solve the problem.
The following is working code for an async/await Alexa Intent.
The full index.js is available on my github if you want to take a look, but that will be a more advanced final version. The code below immediately follows up on the original question.
"RecommendationIntent": async function main () {
// delegate to Alexa to collect all the required slots
let filledSlots = delegateSlotCollection.call(this);
if (!filledSlots) {
return;
}
console.log("filled slots: " + JSON.stringify(filledSlots));
// at this point, we know that all required slots are filled.
let slotValues = getSlotValues(filledSlots);
console.log(JSON.stringify(slotValues));
const mainIngredientQuery = slotValues.mainIngredient.resolved;
const resp = await Yummly.query('chicken')
.maxTotalTimeInSeconds(1400)
.maxResults(20)
.minRating(3)
.get();
const names = resp.matches.map(recipe => recipe.recipeName);
console.log(names);
const speechOutput = names[0];
this.response.speak(speechOutput);
this.emit(":responseReady");
},

Categories

Resources