Function declared in a loop contains unsafe references to variable(s) - javascript

My code checks if a user is available. See snippet below:
const users = ['user1', 'user2', 'user3', 'user4']
const usersToAdd = 2
const getRandomWorker = (userArray) => {
return userArray[Math.floor(userArray.length * Math.random())]
}
const availableUsers = []
for (let i = 0; i < usersToAdd; i += i) {
let randomWorker = getRandomWorker(users)
let didAddWorker = false
while (!didAddWorker) {
if (checkIfUserAvailable(randomWorker)) {
availableUsers.push(randomWorker)
users = users.filter((user) => user !== randomWorker)
didAddWorker = true
} else if (!users.length) {
didAddWorker = true
} else {
users = users.filter((user) => user !== randomWorker)
}
}
}
My only problem is that it contains unsafe references to variables(s) because I get the following error:
Function declared in a loop contains unsafe references to variable(s) randomWorker.
I've searched around and fiddled with my code but I can't get rid of the error. I don't know where to look anymore.

As T.J Crowder suggested I had to make a function outside of the loop:
const filterOut = (array, target) => array.filter(element => element !== target);
That fixed it. A bit strange but I can continue know. Thanks!

Related

Simplify forEach in forEach React

I have a function where I have to return for each "subcontractor" its response for each selection criteria.
Subcontractor object contains a selectionCriteria object. selectionCriteria object contains an array of data for each selectionCriteria a user has responded to.
Each array item is an object, that contains files, id, request (object that contains info about selection criteria user is responding to), response (contains value of the response).
Here is an example of how a subcontractor looks:
This is the function I come up with, but it's quite complex:
const { subcontractors } = useLoaderData<typeof loader>();
const { t } = useTranslation();
const submittedSubcontractors = subcontractors.filter(
(s) => s.status === 'submitted'
);
const subcontractorsResponsesToSelectionCriteria: Array<ISubcontractor> = [];
let providedAnswersResponded: boolean | null = null;
let providedAnswersFiles: Array<IFile> | [] = [];
let providedAnswersRequiresFiles: boolean | null = null;
submittedSubcontractors.forEach((u) => {
u.selectionCriteria.forEach((c) => {
if (c.request.id === criteriaId) {
if (c.response && 'answer' in c.response) {
if (typeof c.response.answer === 'boolean') {
providedAnswersResponded = c.response.answer;
} else {
providedAnswersResponded = null;
}
} else {
providedAnswersResponded = null;
}
providedAnswersFiles = c.files;
providedAnswersRequiresFiles = c.request.are_files_required;
subcontractorsResponsesToSelectionCriteria.push(u as ISubcontractor);
}
});
});
How could I simplify this code by using .reduce() method, or maybe even better ideas?
You should start working on reducing the level of nesting in your if/else like so:
function getProvidedAnswersResponded(response: any) {
if (response && ('answer' in response) && (typeof response.answer === 'boolean')) {
return response.answer;
}
return null;
}
submittedSubcontractors.forEach(u => {
u.selectionCriteria.forEach(c => {
if (c.request.id !== criteriaId) {
return;
}
providedAnswersResponded = getProvidedAnswersResponded(c.response);
providedAnswersFiles = c.files;
providedAnswersRequiresFiles = c.request.are_files_required;
subcontractorsResponsesToSelectionCriteria.push(u);
});
});
The strategy followed was basically to invert the special cases (such as c.requet.id === criteriaId) and exit the function immediately.
Also, extracting the "provided answer responded" function seems atomic enough to move it to a separate block, giving it more verbosity about what that specific code block is doing.

Function parameter returning "undefined"

I'm hoping someone can tell me what I'm doing wrong here. When trying to log the value of artistBox in the hideProductCard function, it's returning 'undefined'. However, when logging the value of artistBox in the addArtistSelection function it returns with the correct value. Why is this happening? Both functions, as seen in the 'if/else if conditionals' are taking the same parameter and it is being passed along to both functions, but only one is working.
// Adding Artist Selection
let artistCheckBoxes = document.querySelectorAll('.artist-filter-input');
let artistProductCards = document.querySelectorAll('.store-card');
artistCheckBoxes.forEach((artistBox) => {
artistBox.addEventListener('click', () => {
if (artistBox.checked === true) {
addArtistSelection(artistBox);
hideProductCard(artistBox);
} else if (artistBox.checked === false) {
document.getElementById(`${artistBox.value}-selection`).remove();
}
});
});
let addArtistSelection = (artistBox) => {
let artistSelectionContainer = document.querySelector('.artist-selection-container');
let artistSelection = document.createElement('p');
artistSelection.classList = 'filter-selection artist-selection';
artistSelection.id = `${artistBox.value}-selection`;
artistSelection.innerHTML = `
${artistBox.value}
`;
artistSelectionContainer.appendChild(artistSelection);
};
let hideProductCard = (artistBox) => {
artistProductCards.forEach((productCard) => {
console.log(productCard.querySelector('.card-title').innerText);
console.log(artistBox.value);
});
};

Unable To Pass Objects/Arrays in IPCRenderer, An object could not be cloned EventEmitter.i.send.i.send

I am unable to pass any object or arrays to IPCRenderer.
I am getting error when passing an object or array through ipcs, I have even tried to send by converting to string using JSON.stringify but it converts it into empty object string.
I have tried passing a fileList, an array of object & even an object nothing passes. only string or handwritten objects are working.
I've read that it uses Structured Clone Algorithm and fileList & Array is allowed by this algorithm
ERROR:
electron/js2c/renderer_init.js:74 Uncaught Error: An object could not be cloned.
at EventEmitter.i.send.i.send (electron/js2c/renderer_init.js:74)
at HTMLButtonElement.compressNow (ImageHandling.js:190)
I have tried many possible solutions but nothing worked
code:
const compressNow = () => {
ipcRenderer.send("image:compress", filess). ///This is the error.
// filess is a variable containing an array of selected files from an HTML input.
}
Now i have tried to send filess as JSON.stringify, i tried to send it as an object but nothing works unless i manually write a dummy object or string.
Here's My Github Repo for this project
Files With ErrorJ:-
ImageHandling.js
const fs = window.require('fs');
const {ipcRenderer} = require("electron")
const SELECT = (target) => document.querySelector(`${target}`)
var filess = []
const imgUploadInput = SELECT("#imgUploadInput")
const warning = SELECT("#warning")
const setImgBase64 = (imgEl, file) => {
const ReadAbleFile = fs.readFileSync(file.path).toString('base64')
let src = "data:image/png;base64," + ReadAbleFile
imgEl.setAttribute("src", src)
// El.src=src
// console.log(`FIXED IMAGE # ${imgEl} `,ReadAbleFile)
}
const renderImages = () => {
const files = filess && Array.from(filess)
const defaultImg = SELECT("#defaultImg")
const addImgBtn = SELECT("#addImgBtn")
imgUploadInput.disabled = true;
let numOfFiles = files.length
if (numOfFiles < 1) {
SELECT("#compressContainer").style.visibility = "hidden"
} else {
SELECT("#compressContainer").style.visibility = "visible"
}
if (numOfFiles > 49) {
warning.innerHTML = `<b style="font-weight:bold; color:red;">WARNING:</b><br/>
<span style="padding:10px;text-align:left">
Your processor/computer may not be able to process ${numOfFiles} Images at once, We recommend selecting less than 50 Images at once for better performance.
</span>
`;
}
addImgBtn.innerHTML = `LOADING.....`
if (defaultImg && numOfFiles > 0)
defaultImg.remove();
setTimeout(() => {
if (files && numOfFiles > 0) {
let displayImages = SELECT("#displayImages")
displayImages.innerHTML = ""
files ?. forEach((file, i) => {
let divEl = document.createElement("div")
let imgEl = document.createElement("img")
imgEl.src = file.path
imgEl.id = `PNG_${i}_${
btoa(file.name)
}`
divEl.className = "displayedImg"
imgEl.setAttribute("onclick", `document.getElementById('ImageView').src=this.src`)
const a = document.createElement("a")
a.appendChild(imgEl)
a.setAttribute("href", `#ViewImage`)
a.className = "perfundo__link"
divEl.appendChild(a)
divEl.className = "displayedImg perfundo"
displayImages.appendChild(divEl)
if (i == files.length - 1) {
warning.innerHTML = "";
updateNumOfImages();
}
imgEl.onerror = () => setImgBase64(imgEl, file) // converting to base64 only on error, this make performance better and help us avoid freezes. (before this i was converting all images to base64 wither errored or not that was making computer freez)
})
addImgBtn.innerHTML = "+ Add MORE"
imgUploadInput.disabled = false
findDuplicate()
}
}, 0);
}
const hasDuplicate=()=>{
let FileNames = [... filess.map(f => f.name)]
let duplicateFiles = filess.filter((file, i) => FileNames.indexOf(file.name) !== i)
return {FileNames,duplicateFiles,FilesLength:duplicateFiles.length}
}
const findDuplicate = (forceAlert = false) => {
if (filess && filess.length) {
let {FileNames} = hasDuplicate()
let {duplicateFiles} = hasDuplicate()
if (duplicateFiles.length) { // alert(``)
let countFiles = duplicateFiles.length
let fileStr = countFiles > 1 ? "files" : "file"
console.log("result from removeDup=> ", filess, " \n dupfilename=> ", FileNames, " \n dupfiles=> ", duplicateFiles)
let shouldNotAsk = localStorage.getItem("NeverAsk")
let msg = `You've selected ${
countFiles > 1 ? countFiles : "a"
} duplicate ${fileStr}`
let duplInner = `<span style='color:red'>
<b>WARNING</b>
<p style="margin:0px;line-height:1"> ${msg} . <button onClick="findDuplicate(true)" type="button" class="btn btn-danger btn-rounded btn-sm">REMOVE DUPLICATE</button></p>
</span>`
if (! shouldNotAsk || forceAlert) {
swal("DUPLICATE FILES DETECTED", `${msg} , Would you like to un-select duplicate ${fileStr} having same name?`, {
icon: 'warning',
dangerMode: true,
buttons: {
cancel: true,
...forceAlert ? {} : {
never: "Never Ask"
},
confirm: "Yes !"
}
}).then((Yes) => {
if (Yes == "never") {
localStorage.setItem("NeverAsk", true)
warning.innerHTML=duplInner
} else if (Yes) {
removeDuplicates()
}
})
} else {
warning.innerHTML=duplInner
}
}
}
}
const removeDuplicates = (showAlert=true) => {
let {FileNames} = hasDuplicate()
let {duplicateFiles} = hasDuplicate()
let duplicateFileNames = duplicateFiles.map(f => f.name)
let uniqueFiles = filess.filter((file) => ! duplicateFileNames.includes(file.name))
filess = [
... uniqueFiles,
... duplicateFiles
]
console.log("result from removeDup=> ", filess, " \n filename=> ", FileNames, " \n dupfiles=> ", duplicateFiles, "\n unique fil=> ", uniqueFiles)
renderImages()
if(showAlert){
swal("DONE", "Removed Duplicate Files ", {icon: 'success'}).then(() =>{
renderImages()
setTimeout(() => {
let hasDuplicateFiles = hasDuplicate().FilesLength
if(hasDuplicate){//Re-check if any duplicate files left after the current removal process.
removeDuplicates(false) //Re-run the function to remove remaining. false will make sure that this alert does not show and the loop does not continue.
}
renderImages()
}, 10);
})
}
}
const updateNumOfImages = () => {
warning.innerHTML = `
<span style="text-align:left; color:green">
Selected ${
filess.length
} Image(s)
</span>
`;
}
const compressNow = () => {
ipcRenderer.send("image:compress", filess)
// alert("WOW")
}
CompressBtn.addEventListener("click", compressNow)
imgUploadInput.addEventListener("change", (e) => {
let SelectedFiles = e.target.files
if (SelectedFiles && SelectedFiles.length) {
filess = [
... filess,
... SelectedFiles
]
renderImages()
}
})
// SELECT("#imgUploadInput").addEventListener("drop",(e)=>console.log("DROP=> ",e))
UPDATE:-
I REPLACED THIS:
const compressNow = () => {
ipcRenderer.send("image:compress",filess)
}
INTO THIS:-
const compressNow = () => {
filess.forEach(file => {
ipcRenderer.send("image:compress",file.path )
});
}
Now here i am sending the files one by one via forEach, actually its sending string "file path" so thats how its working i am still confused why do i have to do this? why can't i send whole fileList i assume that this loop method is a bad practice because it will consume more CPU its one additional loop however it won't be necessary if i am able to send the whole array.
See Behavior Changed: Sending non-JS objects over IPC now throws an exception. DOM objects etc. are not serializable. Electron 9.0 (and newer) throws "object could not be cloned" error when unserializable objects are sent.
In your code, File and FileList are DOM objects.
If you want to avoid using forEach, try this code:
const compressNow = () => {
const paths = filess.map(f => f.path);
ipcRenderer.send("image:compress", paths);
}
Can refer to electron github issue tracker for this issue (already closed)
Error: An object could not be cloned #26338
Docs for ipcRenderer.send(channel, ...args)
This issue mainly comes when we have non-cloneable values like function within an object in data we are sending via IPC, to avoid that we can use JSON.stringify() before sending and JSON.parse() later on receiving end, but doing so will cause to lose some of the values eg:
const obj = {
x :10,
foo : ()=>{
console.log('This is non-cloneable value')
}
}
console.log(JSON.stringify(obj))
output:{"x":10}
Instead of sending the images save them in fs and send the path
The simplest thing that could possibly work is to use lodash cloneDeep()
ipcMain.handle('stuffgetList', async () => {
return _.cloneDeep(await stuffStore.getList())
})
in the windows JSON.stringify()
in the main.js JSON.parse()
Remove :compress from. .send method and try

if else in loop bringing up errors in typescript

I have this function that is supposed to get referral codes from users. User gives a code and the referral code checked if it exists in the database then evaluated if
it does not match the current user, so that one should not refer himself and
it is a match with one of the codes in the database
This code however just does not find a match even if the code given is in the database. If the referral code matches the one of the current user, it works correctly and points that out i.e one cannot refer themselves.
But if the referral code is a match to that of another user which is how a referral system should work, it still says no match.
How can I remove this error
export const getID = functions.https.onCall(async(data, context) => {
const db = admin.firestore();
const usersSnapshot = await db.collection("user").get();
const allUIDs = usersSnapshot.docs.map(doc => doc.data().userID);
const userID = context.auth.uid;
const providedID = "cNx7IuY6rZlR9mYSfb1hY7ROFY2";
//db.collection("user").doc(providedID).collection("referrals").doc(userID);
await check();
function check() {
let result;
allUIDs.forEach(idFromDb => {
if (providedID === idFromDb && (idFromDb === userID)) {
result = "ownmatch";
} else if (providedID === idFromDb && (idFromDb !== userID)) {
result = "match";
} else {
result = "nomatch";
}
});
return result;
}
if (check() === "match") {
return {
message: `Match Found`,
};
} else if (check() === "ownmatch") {
return {
message: `Sorry, you can't use your own invite code`,
};
} else {
return {
message: `No User with that ID`
};
}
});
(This is not an answer, but a simple refactoring.)
This is what your code is currently doing (roughly, I didn't run it):
const resultMsgs = {
nomatch: 'No User With That ID',
ownmatch: 'Sorry, you can\'t use your own invite code',
match: 'Match Found',
}
function check(uids, providedId, userId) {
let result
uids.forEach(idFromDb => {
if (providedId !== idFromDb) {
result = 'nomatch'
return
}
if (userID === idFromDb) {
result = 'ownmatch'
return
}
result = 'match'
})
return result
}
export const getID = functions
.https
.onCall(async (data, context) => {
const userId = context.auth.uid
const providedId = 'cNx7IuY6rZlR9mYSfb1hY7ROFY2'
const db = admin.firestore()
const user = await db.collection('user').get()
const uids = user.docs.map(doc => doc.data().userId)
const checkResult = check(uids, providedId, userId)
return { message: resultMsgs[checkResult] }
})
(I removed the seemingly-spurious db collection operation.)
Your forEach is iterating over all of the uuids, but result will be set to whatever the last comparison was. Perhaps this is correct, but:
If you're looking for any match, this is not what you want.
If you're looking for all matches, this is not what you want.
If you're looking to match the last UUID, it's what you want, but an odd way to go about it.
So:
If you want any matches, use... ahem any form of an any function.
If you want all matches, use any form of an all function.
If you want the first match, then just check the first element.
If you want the complete set of comparisons then you'll need to use map instead of forEach, and handle each result appropriately, whatever that means in your case.
In any event, I'd recommend breaking up your code more cleanly. It'll be much easier to reason about, and fix.

Continue on Null Value of Result (Nodejs, Puppeteer)

I'm just starting to play around with Puppeteer (Headless Chrome) and Nodejs. I'm scraping some test sites, and things work great when all the values are present, but if the value is missing I get an error like:
Cannot read property 'src' of null (so in the code below, the first two passes might have all values, but the third pass, there is no picture, so it just errors out).
Before I was using if(!picture) continue; but I think it's not working now because of the for loop.
Any help would be greatly appreciated, thanks!
for (let i = 1; i <= 3; i++) {
//...Getting to correct page and scraping it three times
const result = await page.evaluate(() => {
let title = document.querySelector('h1').innerText;
let article = document.querySelector('.c-entry-content').innerText;
let picture = document.querySelector('.c-picture img').src;
if (!document.querySelector('.c-picture img').src) {
let picture = 'No Link'; } //throws error
let source = "The Verge";
let categories = "Tech";
if (!picture)
continue; //throws error
return {
title,
article,
picture,
source,
categories
}
});
}
let picture = document.querySelector('.c-picture img').src;
if (!document.querySelector('.c-picture img').src) {
let picture = 'No Link'; } //throws error
If there is no picture, then document.querySelector() returns null, which does not have a src property. You need to check that your query found an element before trying to read the src property.
Moving the null-check to the top of the function has the added benefit of saving unnecessary calculations when you are just going to bail out anyway.
async function scrape3() {
// ...
for (let i = 1; i <= 3; i++) {
//...Getting to correct page and scraping it three times
const result = await page.evaluate(() => {
const pictureElement = document.querySelector('.c-picture img');
if (!pictureElement) return null;
const picture = pictureElement.src;
const title = document.querySelector('h1').innerText;
const article = document.querySelector('.c-entry-content').innerText;
const source = "The Verge";
const categories = "Tech";
return {
title,
article,
picture,
source,
categories
}
});
if (!result) continue;
// ... do stuff with result
}
Answering comment question: "Is there a way just to skip anything blank, and return the rest?"
Yes. You just need to check the existence of each element that could be missing before trying to read a property off of it. In this case we can omit the early return since you're always interested in all the results.
async function scrape3() {
// ...
for (let i = 1; i <= 3; i++) {
const result = await page.evaluate(() => {
const img = document.querySelector('.c-picture img');
const h1 = document.querySelector('h1');
const content = document.querySelector('.c-entry-content');
const picture = img ? img.src : '';
const title = h1 ? h1.innerText : '';
const article = content ? content.innerText : '';
const source = "The Verge";
const categories = "Tech";
return {
title,
article,
picture,
source,
categories
}
});
// ...
}
}
Further thoughts
Since I'm still on this question, let me take this one step further, and refactor it a bit with some higher level techniques you might be interested in. Not sure if this is exactly what you are after, but it should give you some ideas about writing more maintainable code.
// Generic reusable helper to return an object property
// if object exists and has property, else a default value
//
// This is a curried function accepting one argument at a
// time and capturing each parameter in a closure.
//
const maybeGetProp = default => key => object =>
(object && object.hasOwnProperty(key)) ? object.key : default
// Pass in empty string as the default value
//
const getPropOrEmptyString = maybeGetProp('')
// Apply the second parameter, the property name, making 2
// slightly different functions which have a default value
// and a property name pre-loaded. Both functions only need
// an object passed in to return either the property if it
// exists or an empty string.
//
const maybeText = getPropOrEmptyString('innerText')
const maybeSrc = getPropOrEmptyString('src')
async function scrape3() {
// ...
// The _ parameter name is acknowledging that we expect a
// an argument passed in but saying we plan to ignore it.
//
const evaluate = _ => page.evaluate(() => {
// Attempt to retrieve the desired elements
//
const img = document.querySelector('.c-picture img');
const h1 = document.querySelector('h1')
const content = document.querySelector('.c-entry-content')
// Return the results, with empty string in
// place of any missing properties.
//
return {
title: maybeText(h1),
article: maybeText(article),
picture: maybeSrc(img),
source: 'The Verge',
categories: 'Tech'
}
}))
// Start with an empty array of length 3
//
const evaluations = Array(3).fill()
// Then map over that array ignoring the undefined
// input and return a promise for a page evaluation
//
.map(evaluate)
// All 3 scrapes are occuring concurrently. We'll
// wait for all of them to finish.
//
const results = await Promise.all(evaluations)
// Now we have an array of results, so we can
// continue using array methods to iterate over them
// or otherwise manipulate or transform them
//
results
.filter(result => result.title && result.picture)
.forEach(result => {
//
// Do something with each result
//
})
}
Try-catch worked for me:
try {
if (await page.$eval('element')!==null) {
const name = await page.$eval('element')
}
}catch(error){
name = ''
}

Categories