unable to set proper scope for variable within a JavaScript promise - javascript

I've come across a weird issue where a new variable is being created in local scope even if it is defined outside,
from the below code
after I call buildMeta() and check the contents of "data", it is always empty
implying that it's not being modified at all, even if I've specifically targeted "that.data" where that refers to the class' object.
I'd appreciate if anyone would point out what I am doing wrong.
class meta {
constructor(files) {
if(!files) throw Error("files not specified");
this.data = {};
this.ls = files;
}
buildMeta() {
var that = this;
for(let i = 0; i < that.ls.length; i++) {
mm.parseFile(that.ls[i]).then(x => {
var info = x.common;
that.data[info.artist] = "test";
}).catch((x) => {
console.log(x);
});
}
}
}
const mm = new meta(indexer); // indexer is an array of file paths
mm.buildMeta();
console.log(mm.data);

You're mixing sync with async code here.
The for loop won't wait for the parseFile promises to resolve.
You could use Promise.all to fill in the data when the files are parsed.
// Class names should be written using a capital letter
class Meta {
...
buildMeta() {
// You don't need this assignment since you're using arrow functions
// var that = this;
const promises = this.ls.map(filePath => mm.parseFile(filePath));
return Promise.all(promises).then(resolvedPromises => {
resolvedPromises.map(({ parsedFile }) => {
this.data[parsedFile.common.artist] = "test";
});
return this.data;
}).catch(console.error);
}
...
const mm = new meta(indexer); // indexer is an array of file paths
mm.buildMeta().then(data => {console.log(data)});
Hope this helps.

You are logging mm.data before parseFile has finished. Your code implies that it returns a promise, so your insertion into that.data will happen after your console.log(mm.data) executes.
You need to return a promise from buildMeta, so that you can do...
const mm = new meta(indexer);
mm.buildMeta().then(() => {
console.log(mm.data);
})
Here's a buildMeta that should do what you need. This returns a promise that waits for all of the parseFile invocations to do their work and update this.data...
buildMeta() {
return Promise.all(this.ls.map(f => mm.parseFile(f).then(x => {
var info = x.common;
this.data[info.artist] = "test";
})))
}

Related

filtering an array issue

I'm importing an array into a module, and adding and removing items from that array. when I give a push, it adds the item to the array globally, so much so that if I use that same array in another module, it will include this item that I pushed. but when I try to filter, with that same array getting itself with the filter, it only removes in that specific module. How can I make it modify globally?
let { ignore } = require('../utils/handleIgnore');
const questions = require('./quesiton');
const AgendarCollector = async (client, message) => {
ignore.push(message.from);
let counter = 0;
const filter = (m) => m.from === message.from;
const collector = client.createMessageCollector(message.from, filter, {
max: 4,
time: 1000 * 60,
});
await client.sendText(message.from, questions[counter++]);
collector.on('start', () => {});
await collector.on('collect', async (m) => {
if (m) {
if (counter < questions.length) {
await client.sendText(message.from, questions[counter++]);
}
}
});
await collector.on('end', async (all) => {
ignore = ignore.filter((ignored) => ignored !== message.from);
console.log(ignore);
const finished = [];
if (all.size < questions) {
console.log('não terminado');
}
await all.forEach((one) => finished.push(` ${one.content}`));
await client.sendText(message.from, `${finished}.\nConfirma?`);
});
};
module.exports = AgendarCollector;
see, in this code, import the ignore array and i push an item to then when the code starts and remove when its end.
but the item continues when I check that same array in another module.
I tried to change this array ignore by using functions inside this module but still not working
let ignore = [];
const addIgnore = (message) => {
ignore.push(message.from);
};
const removeIgnore = (message) => {
ignore = ignore.filter((ignored) => ignored !== message.from);
console.log(ignore);
};
console.log(ignore);
module.exports = { ignore, addIgnore, removeIgnore };
You are using the variables for import and export and hence cought up with issues.
Instead, make use of getters.
Write a function which will return the array of ignore. something like this:
const getIgnoredList = () => {
return ignore;
};
and in your first code, import getIgnoredList and replace ignore with getIgnoredList()
Explanation :
Whenever we import the variables only the value at that particular time will be imported and there will not be any data binding. Hence there won't be any change in the data even though you think you are updating the actual data.
When you use require(...) statement it's executed only once. Hence when you try to access the property it gives the same value everytime.
Instead you should use getters
let data = {
ignore : [],
get getIgnore() {
return this.ignore
}
}
module.export = {
getIgnore: data.getIgnore,
}
Then wherever you want to access ignore do
var {getIgnore}= require('FILE_NAME')
Now: console.log(getIgnore) will invoke the getter and give you current value of ignore
Using getters will allow you to access particular variables from other modules but if you want to make changes in value of those variables from other module you have to use setter.
More about getters here
More about setters here

How to send vars to a function from module.exports and get some back?

I'm quite new to javascript and i'm trying to send one variable to a function, call an api from there and get the stats back into module.exports but I cant seem to figure it out....
Here's basically how my code is built:
function stuff(userArg) {
(calling api magic here)
var index = -1;
for(var i = 0; i < Object.keys(res['data']['playerstats']['stats']).length; i++) {
if(res['data']['playerstats']['stats'][i]['name'] === 'deaths') {
index = i;
var userDeaths = res['data']['playerstats']['stats'][index]['value'];
break;
}
}
return userDeaths;
}
module.exports = {
name: 'table',
description: '.....'
async run(messages, args) {
const userArg = args.join(" ");
stuff(userArg);
console.log(userDeaths);
}
}
It seems like it should work but I cant get it to work and its driving me mad... If anyone knows the reason i'd greatly apppreciate it!
I don't think this has anything to do with module exports. You just forgot to assign the return value of the stuff() call to a local variable. Notice that userDeaths is not a global variable, you declared it in stuff, which means you cannot use it in run.
function run(messages, args) {
const userArg = args.join(" ");
const userDeaths = stuff(userArg);
// ^^^^^^^^^^^^^^^^^^
console.log(userDeaths);
}

Observable value inside subscribe is undefined when chaning observables

I have a function from a certain library that returns an Observable that I want to call from another function. I have a need to propagate that Observable into multiple function calls. Here is how my code is structured:
extractSignature = (xml, signatureCount = 1) => {
const observ = this.generateDigest(signedContent, alg).pipe(map(digest => {
const sigContainer = {
alg: alg,
signature: signatureValue,
signedContent: signedContent,
digest: digest
};
console.log('sigContainer inside pipe: ');
console.log(sigContainer);
return sigContainer;
}));
return observ;
}
dissasemble(xml): Observable<SignatureContainerModel[]> {
const observables: Observable<any>[] = [];
for (let i = 1; i <= count; i++) {
const extractSigObservable = this.extractSignature(xml, i);
console.log('extractSigObs inside pipe: ');
console.log(extractSigObservable);
const observ = extractSigObservable.pipe(map(sigContainer => {
console.log('sigContainer inside pipe: ');
console.log(sigContainer);
const hashContainers: HashContainerModel[] = [];
const hashContainer: HashContainerModel = new HashContainerModel();
hashContainer.digestAlgorithm = sigContainer.alg;
hashContainer.bytes = sigContainer.digest;
hashContainers.push(hashContainer);
const signatureContainer: SignatureContainerModel = {
hashContainers: hashContainers,
signature: sigContainer.signature
};
console.log('observable inside pipe: ');
console.log(observ);
}));
observables.push(observ);
}
return forkJoin(observables);
}
verify() {
this.sigExec.dissasemble(this.fileContent).subscribe((signatureContainers: SignatureContainerModel[]) => {
// signatureContainers is [undefined] here
console.log('Sig Containers: ');
console.log(signatureContainers);
this.verifyHash(signatureContainers);
});
}
signatureContainers variable is [undefined] inside the subscribe. I'm not sure what the problem is since when I check all the logs that I wrote inside map functions they seem fine
RXJS Documentation on forkJoin:
Be aware that if any of the inner observables supplied to forkJoin error you will lose the value of any other observables that would or have already completed if you do not catch the error correctly on the inner observable. If you are only concerned with all inner observables completing successfully you can catch the error on the outside.
https://www.learnrxjs.io/learn-rxjs/operators/combination/forkjoin
There a possibility you are erroring out inside your pipe and those values are being lost.
Also, I noticed that you're not returning anything from your pipe. This could also be an issue.

React.js. How to change keyword 'this'

i am trying to get every image download URL from Firebase. It seems that the first 'this' is different from the second one. If i want to let the second 'this' equals to the value of the first, what should i do? Thank you so much!
getAllURL = product => {
// Get all the images from the firebase
var storage = firebase.storage();
console.log(this) // first this
const storageRef = storage.ref(`image/${product}`)
storageRef.listAll().then(function(result) {
result.items.forEach(function(imageRef) {
imageRef.getDownloadURL().then(function(url) {
console.log(this) // second this is undefined
}).catch(function(error) {});
})
})
}
componentDidMount() {
axios.get('/user/product')
.then(res=>{
if(res.data.code==0) {
this.setState({data:res.data.data},function(){
for (var i = 0; i < this.state.data.length; i++){
this.getAllURL(this.state.data[i].productName)
}
})
}
})
}
this is one the most confusing features in Javascript. I'd recommended you to look more into the topic.
As for a shortcut, there're many ways to take care of that.
First method: just assigned the first this into some variable first.
getAllURL = product => {
// Get all the images from the firebase
var storage = firebase.storage();
console.log(this) // first this
var firstThis = this; // some people prefered to assign "var that = this;", lol
const storageRef = storage.ref(`image/${product}`)
storageRef.listAll().then(function(result) {
result.items.forEach(function(imageRef) {
imageRef.getDownloadURL().then(function(url) {
console.log(firstThis); // use the new variable to refer to the firstThis
}).catch(function(error) {});
});
});
}
Second method: make use of bind function in javascript (a bit more advanced, and much better received from a functional programming perspective)
getAllURL = product => {
// Get all the images from the firebase
var storage = firebase.storage();
console.log(this) // first this
const storageRef = storage.ref(`image/${product}`)
storageRef.listAll().then((function(result) {
result.items.forEach((function(imageRef) {
imageRef.getDownloadURL().then((function(url) {
console.log(this);
}).bind(this)).catch(function(error) {}); // yet another binding to propagate "this" inside
}).bind(this)); // another binding to force "this" on this yet another inline function to equal the first this (propagate it down)
}).bind(this)); // this binding will force "this" inside this inline function to equals the firstThis
}
Note: It might get less confusing if amount of inline functions is reduced
getAllURL = product => {
// Get all the images from the firebase
var storage = firebase.storage();
console.log(this) // first this
const storageRef = storage.ref(`image/${product}`)
storageRef.listAll().then(listAllCallback.bind(this));
}
function listAllCallback(result) {
for (var i = 0; i<result.items.length; i++) {
var imageRef = result.items[i];
imageRef.getDownloadURL()
.then(downloadUrlCallback.bind(this))
.catch(function(error) {});
}
}
function downloadUrlCallback(url) {
console.log(this); // second this
}

How to give a function a return value when the return is inside an if statement

I had a function that was initially like this, and worked:
productGroupPages(): PDF.Page[] {
const descriptions = {};
this.areas.forEach(area => descriptions[area.id] = area.description);
return this.json.engagementAreas.map(area => {
return this.productGroupPage(area, descriptions[area.id]);
});
}
Basically everything isnt wrapped in the for each.
I had to alter to the function as i wanted to go through a for each, to then use an if statement so i would only return values that actually contained a certain value, the result is that my return statement is too early, I cant move it to the end because of the for loop and im struggling to find out how I can get past this,
this new function:
productGroupPages(): PDF.Page[] {
const descriptions = {};
let pages = {};
console.log('this .json' + JSON.stringify(this.json))
this.json.engagementAreas.forEach(area => {
descriptions[area.id] = area.description
if (area.engagementTypes.length !== 0) {
return this.json.engagementAreas.map(area => {
return this.productGroupPage(area, descriptions[area.id]);
});
}
})
}
I tried creating a variable, an array or object and equaling that to the return value and then returning that near the end of the scope but this wouldnt let me it said the return type was wrong.
Any help would be greatly appreciated.
I think your initial code, with the forEach and map separated was very clean!
The problem with your new code, is that using return inside .forEach() does not make sense in javascript ;) Whenever you return from .forEach(), javascript just ignores the result:
let result = [1,2,3].forEach(x => {
return x * 2;
});
result // undefined
I think you wanted to only return some area-s, which you can do with array.filter() like this:
productGroupPages(): PDF.Page[] {
const descriptions = {};
this.areas.forEach(area => descriptions[area.id] = area.description);
return this.json.engagementAreas
.filter(area => area.engagementTypes.length !== 0)
.map(area => {
return this.productGroupPage(area, descriptions[area.id]);
});
}
I hope that is actually what you meant to do, and if so, I hope this works :)

Categories