I am facing a difficulty in obtaining the property value from a javascript class object.
The following is the class files and how I try to get its property value:
1: I defined a class named School in classes.js
export class School{
constructor()
{
this.schools = []; //to store schools returned from backend
}
//used to get all schools
getall()
{
axios.get('/api/schools')
.then(response =>{
this.schools = response.data;
console.error('School length: ' + this.schools.length);
this.ok = true;
//add origin data property
for(let i = 0; i < this.schools.length; i++)
{
this.schools[i]['orgValue'] = new tagChange();
}
})
.
catch(error => {
this.ok = false;
this.error = error.status;
this.schools = [];
});
return this.schools;
} //getall
}
2: I import this School class to another js file
//import School class
import {tagChange, School} from './classes.js';
export default {
components: {
modalAddSchool,
},
data() {
return {
schools: [], // my schools in
mySchoolObj: new School(), //create a School object
}
},
methods: {
getAllSchools()
{
//after this call, mySchoolObj's schools property indeed contain 8 elements in array format,
//However, this.schools can not be assigned successfully, so unexplainable
this.schools = this.mySchoolObj.getall();
//print zero element
console.log(this.mySchoolObj.schools);
//print zero element
console.log(this.schools)
}
},
3: after call getAllSchools() method, the mySchoolObj.schools indeed contain 8 school elements but this.schools cannot be assigned successfully, neither the following two console.log call can only print zero length
4: I really want to know how to return all the mySchoolObj.schools to this.schools, and how to get/visit its other property value?
axios.get is asynchronous, it means that when you return this.schools;, the ajax call is not finished yet so you return an empty array [].
More informations here:
Synchronous and asynchronous requests
You can return the promise given by axios or use a callback, like that:
//used to get all schools
getall(callback) { // take a callback function
axios.get('/api/schools')
.then(response =>{
this.schools = response.data;
console.error('School length: ' + this.schools.length);
this.ok = true;
//add origin data property
for (let i = 0; i < this.schools.length; i++) {
this.schools[i]['orgValue'] = new tagChange();
}
if (typeof callback === 'function') {
callback(this.schools, null); // call the callback with data and null (null because there is no error)
}
})
.catch(error => {
this.ok = false;
this.error = error.status;
this.schools = [];
if (typeof callback === 'function') {
callback(null, error.status); // call the callback with null data and the error status
}
});
return this.schools;
}
Then you can use your method like that:
methods: {
getAllSchools() {
this.mySchoolObj.getall((data, error) => {
if (error) {
return console.log(error);
}
this.schools = data;
console.log(this.schools);
});
}
},
(this code isn't tested, it may contain bugs)
Related
I have this object
let x = {
"name": "Ola",
"dates": [
{
"7.01.2020": [1, 2, 3]
},
{
"8.01.2020": [3 ,4]
}
],
"id": 7
}
and I need to be able to delete on button click chosen element from array. Ex. after button click, my object looks like this:
let x = {
"name": "Ola",
"dates": [
{
"7.01.2020": [1, 2]
},
{
"8.01.2020": [3 ,4]
}
],
"id": 7
}
Acrtually I've managed to filter this arr, the problem is when I try to return new x.dates arr. Please, notice that there are objects with different keys.
Any ideas? Thanks!
EDIT - whole func
try {
fetch(`http://localhost:3003/users/${userObject.id}`)
.then(res => res.json())
.then(user => {
const map = new Map(
user.dates.map((entry, i, arr) => {
const [key] = Object.keys(entry);
console.log(entry);
return [key, entry[key]];
})
);
result = map.get(date.toLocaleDateString());
console.log(user.dates);
return user;
})
.then(user => {
// Create array without deleted task
userDatesArr = result.filter(el => {
if (el !== itemText) {
return el;
}
});
result = userDatesArr;
// Update json-server - doesn't work, cause user.dates looks the same
patchFunc(user.dates);
});
} catch (err) {
console.error(err);
}
;
There are a few issues with that code:
You're assigning to a variable that isn't declared anywhere (result). That means the code is falling prey to what I call The Horror of Implicit Globals. You need to declare your variables. I strongly recommend turning on strict mode so the JavaScript engine will tell you when you do this.
That's now how you use filter, the callback should return a flag saying whether to keep the entry.
You're not checking for HTTP success. This is a footgun in the fetch API I write about here. You need to check ok on the response before calling json.
itemText is a string, but your array contains numbers. No string is ever === a number.
There is no reason for separating the code in your first then handler from the second, there's no intervening promise.
There's no reason to create the Map if you're not going to reuse it. You can just find the object once.
Nothing in the code handles errors the ajax call or the fulfillment handlers. (The try/catch you have wrapped around it will only catch errors calling fetch, not errors in the fetch operation of subsequent then handlers.)
Here's an updated version with some notes:
fetch(`http://localhost:3003/users/${userObject.id}`)
.then(res => {
// *** Check for HTTP success
if (!res.ok) {
throw new Error("HTTP error " + res.status);
}
return res.json();
})
.then(user => {
const targetValue = +itemText; // *** Convert to number
for (const entry of user.dates) {
const targetKey = Object.keys(entry)[0];
if (targetKey === key) {
// *** Remove the entry if present
entry[targetKey] = entry[targetKey].filter(value => value !== targetValue);
break;
}
}
// Update json-server
patchFunc(user.dates);
});
Note that that doesn't flag it up if the object for the target date isn't found. If you want to do that, you can add a flag:
fetch(`http://localhost:3003/users/${userObject.id}`)
.then(res => {
// *** Check for HTTP success
if (!res.ok) {
throw new Error("HTTP error " + res.status);
}
return res.json();
})
.then(user => {
const targetValue = +itemText; // *** Convert to number
let found = false;
for (const entry of user.dates) {
const targetKey = Object.keys(entry)[0];
if (targetKey === key) {
// *** Remove the entry if present
entry[targetKey] = entry[targetKey].filter(value => value !== targetValue);
found = true;
break;
}
}
if (!found) {
// *** Handle the fact it wasn't found
}
// Update json-server
patchFunc(user.dates);
});
You'll also want to add a rejection handler (.catch) to handle errors.
I think the below code snippet will give a better understanding of the problem.
Check the console, you will get the desired output. You need to do a deep cloning to avoid mutating the existing object (Deep Cloning - thanks to #nemisj)
let x = {
"name": "Ola",
"dates": [{
"7.01.2020": [1, 2, 3]
},
{
"8.01.2020": [3, 4]
}
],
"id": 7
}
function clone(item) {
if (!item) { return item; } // null, undefined values check
var types = [ Number, String, Boolean ],
result;
// normalizing primitives if someone did new String('aaa'), or new Number('444');
types.forEach(function(type) {
if (item instanceof type) {
result = type( item );
}
});
if (typeof result == "undefined") {
if (Object.prototype.toString.call( item ) === "[object Array]") {
result = [];
item.forEach(function(child, index, array) {
result[index] = clone( child );
});
} else if (typeof item == "object") {
// testing that this is DOM
if (item.nodeType && typeof item.cloneNode == "function") {
result = item.cloneNode( true );
} else if (!item.prototype) { // check that this is a literal
if (item instanceof Date) {
result = new Date(item);
} else {
// it is an object literal
result = {};
for (var i in item) {
result[i] = clone( item[i] );
}
}
} else {
// depending what you would like here,
// just keep the reference, or create new object
if (false && item.constructor) {
// would not advice to do that, reason? Read below
result = new item.constructor();
} else {
result = item;
}
}
} else {
result = item;
}
}
return result;
}
function deleteData (date, elementToBeDel) {
let newObj = clone(x) // spreading to avoid mutating the object
newObj.dates.map(dt => {
if(Object.keys(dt)[0] === date){
if(dt[date].indexOf(elementToBeDel) > -1){
dt[date].splice(dt[date].indexOf(elementToBeDel), 1);
}
}
})
console.log("old Object", x)
console.log("new Object",newObj)
}
<button onclick="deleteData('7.01.2020', 3)">Click Me to delete</button>
Brief Explanation: I have fetched result in variable res using **js**.
Result of res on console is shown below.
see here
Requirement: I want to get the value of res in angular variable. I have declared
resarry = [];
When i do
this.resarry = res;
console.log(this.resaary);
Error coming - Cannot set property of 'resarray` undefined.
console.log(results); // no problem in this line
console.log(this.resarry); // giving error
export class HomePage {
resarry = [];
constructor(){
var connection = new JsStore.Instance();
var dbName = 'Demo';
connection.openDb(dbName);
connection.select({
from: Test1,
}).then(function(res) {
// results will be array of objects
console.log(res,'results');
this.resarry = results;
console.log(results); // no problem in this line
console.log(this.resarry); // giving error
}).catch(function(err) {
console.log(err, 'error');
alert(err.message);
});
}
}
Change:
connection.select({
from: Test1,
}).then(function(res) {
// ...
});
to:
connection.select({
from: Test1,
}).then(res => {
// ...
});
Basically, function() { ... } can't access this from the outer scope, while arrow functions can. A more in-depth explanation can be found here.
Also, arrow functions' docs.
resarry:number[] = new Array();
will initialize an empty array which is not undefined.
you can set the type where your result is expected to be.
Because, the "this" meant to be the current function object. So the "this", you have used in the constructor is not the actual component
Use Arrow function or
constructor(){
var connection = new JsStore.Instance();
var dbName = 'Demo';
connection.openDb(dbName);
var $this = this;
connection.select({
from: Test1,
}).then(function(res) {
// results will be array of objects
console.log(res,'results');
$this.resarry = results;
console.log(results); // no problem in this line
console.log($this.resarry); // giving error
}).catch(function(err) {
console.log(err, 'error');
alert(err.message);
});
}
connection.select({
from: Test1,
}).then(function(res) { // results will be array of objects
console.log(res,'results');
this.resarry = results; // perhaps like that => this.resarry = res
console.log(results); // i think, you should have an error on this line because **results** isn't a variable but a string in your console
console.log(this.resarry); // giving error
})
User arrow functions as well as typescript types to validate things before assigning
export class HomePage {
resarry: any[] = []; //--> resarry: any[] -> used to set the type as array
constructor() {
let connection = new JsStore.Instance(); //-> use Let insted of Var
let dbName = 'Demo';
connection.openDb(dbName);
connection.select({
from: "Test1",
}).then(res => {
console.log(res);
if (res)
if (res.length > 0){
this.resarry = res;
console.log(this.resarry);
console.log("connection Successful with array objects");
}else{
console.log("connection Successful without array objects");
}
}), err => {
console.log("connection error");
};
}
}
I'm using an HTTP-triggered Firebase cloud function to make an HTTP request. I get back an array of results (events from Meetup.com), and I push each result to the Firebase realtime database. But for each result, I also need to make another HTTP request for one additional piece of information (the category of the group hosting the event) to fold into the data I'm pushing to the database for that event. Those nested requests cause the cloud function to crash with an error that I can't make sense of.
const functions = require("firebase-functions");
const admin = require("firebase-admin");
admin.initializeApp();
const request = require('request');
exports.foo = functions.https.onRequest(
(req, res) => {
var ref = admin.database().ref("/foo");
var options = {
url: "https://api.meetup.com/2/open_events?sign=true&photo-host=public&lat=39.747988&lon=-104.994945&page=20&key=****",
json: true
};
return request(
options,
(error, response, body) => {
if (error) {
console.log(JSON.stringify(error));
return res.status(500).end();
}
if ("results" in body) {
for (var i = 0; i < body.results.length; i++) {
var result = body.results[i];
if ("name" in result &&
"description" in result &&
"group" in result &&
"urlname" in result.group
) {
var groupOptions = {
url: "https://api.meetup.com/" + result.group.urlname + "?sign=true&photo-host=public&key=****",
json: true
};
var categoryResult = request(
groupOptions,
(groupError, groupResponse, groupBody) => {
if (groupError) {
console.log(JSON.stringify(error));
return null;
}
if ("category" in groupBody &&
"name" in groupBody.category
) {
return groupBody.category.name;
}
return null;
}
);
if (categoryResult) {
var event = {
name: result.name,
description: result.description,
category: categoryResult
};
ref.push(event);
}
}
}
return res.status(200).send("processed events");
} else {
return res.status(500).end();
}
}
);
}
);
The function crashes, log says:
Error: Reference.push failed: first argument contains a function in property 'foo.category.domain._events.error' with contents = function (err) {
if (functionExecutionFinished) {
logDebug('Ignoring exception from a finished function');
} else {
functionExecutionFinished = true;
logAndSendError(err, res);
}
}
at validateFirebaseData (/user_code/node_modules/firebase-admin/node_modules/#firebase/database/dist/index.node.cjs.js:1436:15)
at /user_code/node_modules/firebase-admin/node_modules/#firebase/database/dist/index.node.cjs.js:1479:13
at Object.forEach (/user_code/node_modules/firebase-admin/node_modules/#firebase/util/dist/index.node.cjs.js:837:13)
at validateFirebaseData (/user_code/node_modules/firebase-admin/node_modules/#firebase/database/dist/index.node.cjs.js:1462:14)
at /user_code/node_modules/firebase-admin/node_modules/#firebase/database/dist/index.node.cjs.js:1479:13
at Object.forEach (/user_code/node_modules/firebase-admin/node_modules/#firebase/util/dist/index.node.cjs.js:837:13)
at validateFirebaseData (/user_code/node_modules/firebase-admin/node_modules/#firebase/database/dist/index.node.cjs.js:1462:14)
at /user_code/node_modules/firebase-admin/node_modules/#firebase/database/dist/index.node.cjs.js:1479:13
at Object.forEach (/user_code/node_modules/firebase-admin/node_modules/#firebase/util/dist/index.node.cjs.js:837:13)
at validateFirebaseData (/user_code/node_modules/firebase-admin/node_modules/#firebase/database/dist/index.node.cjs.js:1462:14)
If I leave out the bit for getting the group category, the rest of the code works fine (just writing the name and description for each event to the database, no nested requests). So what's the right way to do this?
I suspect this issue is due to the callbacks. When you use firebase functions, the exported function should wait on everything to execute or return a promise that resolves once everything completes executing. In this case, the exported function will return before the rest of the execution completes.
Here's a start of something more promise based -
const functions = require("firebase-functions");
const admin = require("firebase-admin");
admin.initializeApp();
const request = require("request-promise-native");
exports.foo = functions.https.onRequest(async (req, res) => {
const ref = admin.database().ref("/foo");
try {
const reqEventOptions = {
url:
"https://api.meetup.com/2/open_events?sign=true&photo-host=public&lat=39.747988&lon=-104.994945&page=20&key=xxxxxx",
json: true
};
const bodyEventRequest = await request(reqEventOptions);
if (!bodyEventRequest.results) {
return res.status(200).end();
}
await Promise.all(
bodyEventRequest.results.map(async result => {
if (
result.name &&
result.description &&
result.group &&
result.group.urlname
) {
const event = {
name: result.name,
description: result.description
};
// get group information
const groupOptions = {
url:
"https://api.meetup.com/" +
result.group.urlname +
"?sign=true&photo-host=public&key=xxxxxx",
json: true
};
const categoryResultResponse = await request(groupOptions);
if (
categoryResultResponse.category &&
categoryResultResponse.category.name
) {
event.category = categoryResultResponse.category.name;
}
// save to the databse
return ref.push(event);
}
})
);
return res.status(200).send("processed events");
} catch (error) {
console.error(error.message);
}
});
A quick overview of the changes -
Use await and async calls to wait for things to complete vs. being triggered in a callback (async and await are generally much easier to read than promises with .then functions as the execution order is the order of the code)
Used request-promise-native which supports promises / await (i.e. the await means wait until the promise returns so we need something that returns a promise)
Used const and let vs. var for variables; this improves the scope of variables
Instead of doing checks like if(is good) { do good things } use a if(isbad) { return some error} do good thin. This makes the code easier to read and prevents lots of nested ifs where you don't know where they end
Use a Promise.all() so retrieving the categories for each event is done in parallel
There are two main changes you should implement in your code:
Since request does not return a promise you need to use an interface wrapper for request, like request-promise in order to correctly chain the different asynchronous events (See Doug's comment to your question)
Since you will then call several times (in parallel) the different endpoints with request-promise you need to use Promise.all() in order to wait all the promises resolve before sending back the response. This is also the case for the different calls to the Firebase push() method.
Therefore, modifying your code along the following lines should work.
I let you modifying it in such a way you get the values of name and description used to construct the event object. The order of the items in the results array is exactly the same than the one of the promises one. So you should be able, knowing that, to get the values of name and description within results.forEach(groupBody => {}) e.g. by saving these values in a global array.
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
var rp = require('request-promise');
exports.foo = functions.https.onRequest((req, res) => {
var ref = admin.database().ref('/foo');
var options = {
url:
'https://api.meetup.com/2/open_events?sign=true&photo-host=public&lat=39.747988&lon=-104.994945&page=20&key=****',
json: true
};
rp(options)
.then(body => {
if ('results' in body) {
const promises = [];
for (var i = 0; i < body.results.length; i++) {
var result = body.results[i];
if (
'name' in result &&
'description' in result &&
'group' in result &&
'urlname' in result.group
) {
var groupOptions = {
url:
'https://api.meetup.com/' +
result.group.urlname +
'?sign=true&photo-host=public&key=****',
json: true
};
promises.push(rp(groupOptions));
}
}
return Promise.all(promises);
} else {
throw new Error('err xxxx');
}
})
.then(results => {
const promises = [];
results.forEach(groupBody => {
if ('category' in groupBody && 'name' in groupBody.category) {
var event = {
name: '....',
description: '...',
category: groupBody.category.name
};
promises.push(ref.push(event));
} else {
throw new Error('err xxxx');
}
});
return Promise.all(promises);
})
.then(() => {
res.send('processed events');
})
.catch(error => {
res.status(500).send(error);
});
});
I made some changes and got it working with Node 8. I added this to my package.json:
"engines": {
"node": "8"
}
And this is what the code looks like now, based on R. Wright's answer and some Firebase cloud function sample code.
const functions = require("firebase-functions");
const admin = require("firebase-admin");
admin.initializeApp();
const request = require("request-promise-native");
exports.foo = functions.https.onRequest(
async (req, res) => {
var ref = admin.database().ref("/foo");
var options = {
url: "https://api.meetup.com/2/open_events?sign=true&photo-host=public&lat=39.747988&lon=-104.994945&page=20&key=****",
json: true
};
await request(
options,
async (error, response, body) => {
if (error) {
console.error(JSON.stringify(error));
res.status(500).end();
} else if ("results" in body) {
for (var i = 0; i < body.results.length; i++) {
var result = body.results[i];
if ("name" in result &&
"description" in result &&
"group" in result &&
"urlname" in result.group
) {
var groupOptions = {
url: "https://api.meetup.com/" + result.group.urlname + "?sign=true&photo-host=public&key=****",
json: true
};
var groupBody = await request(groupOptions);
if ("category" in groupBody && "name" in groupBody.category) {
var event = {
name: result.name,
description: result.description,
category: groupBody.category.name
};
await ref.push(event);
}
}
}
res.status(200).send("processed events");
}
}
);
}
);
I'm using two chained firebase requests in my code. the first one is fetching all the reviews, then I iterate trough all the results, firing a second firebase request for each element in loop.
After the loop, I've updated the state with the new data using setState. And that update is making my GUI transparent, like so:
the bottom part is randomly transparent each time, sometimes the render is visible a bit, sometimes not at all. When I remove the setState block, everything is fine.
The code in question:
getReviews() {
let reviewData = {}
let vendorId = this.props.item.vendor_id
let product = this.props.item.key
firebase.database().ref('u_products/' + vendorId + '/' + product + '/reviews/').once('value', (data) => {
reviewData = data.val()
if (!reviewData) {
this.setState({
loadedReviews: true
})
return
} else {
for (var key in reviewData) {
firebase.database().ref('users/' + reviewData[key].poster_uid + '/').once('value', (data) => {
let userData = data.val()
if (userData) {
this.getUserImageUrl()
...
}
})
}
this.state.reviewData = reviewData
this.setState({
dataSource: this.state.dataSource.cloneWithRows(reviewData),
loadedReviews: true,
})
}
})
}
Intended behaviour is first firebase request -> second one iterating for all the results from the first request ->setState.
Does anyone else have this issue?
firebase's .once() returns a Promise, so what you need to do is create an array of Promises, then call Promise.all(array).then((arrayOfData) => { ... });
You can now process the resulting array, then call .setState()
Here's a mockup:
/* firebase mockup */
function Result(url) {
this.url = url;
this.once = function(param, callback) {
return new Promise((resolve, reject) => {
setTimeout(() => {
var data = {
val: () => {
return this.url + " query result";
}
};
resolve(data);
}, Math.random() * 500 + 1000);
});
};
}
var firebase = {
database: function() {
return firebase;
},
ref: function(url) {
return new Result(url);
}
};
var reviewData = {
"test": {
poster_uid: "testData"
},
"2nd": {
poster_uid: "2ndData"
}
};
// ACTUAL IMPORTANT CODE BELOW #################
var arrayOfPromises = [];
for (var key in reviewData) {
arrayOfPromises.push(firebase.database().ref('users/' + reviewData[key].poster_uid + '/').once('value'));
}
Promise.all(arrayOfPromises).then(function(arrayOfResults) {
arrayOfResults.map((data, i) => {
let userData = data.val();
if (userData) {
console.log(i, userData);
}
});
console.log("setting state now");
});
I have an Angular component that uses a type, what would be an easily readable solution to get one data on one case and the other in another?
The component could also be separated in two components, the phone component and the email component, but most of the logic albeit small would be duplicated.
var getContactInfo, hasContactInfo;
if(type === 'email') {
getContactInfo = function (profile) { return profile.getEmail()};
hasContactInfo = function (profile) { return profile.hasEmail()};
} else if(scope.type === 'phone') {
getContactInfo = function (profile) { return profile.getPhone()};
hasContactInfo = function (profile) { return profile.hasPhone()};
}
I would probably have used an object mapping the methods depending on the type:
const buildContactInfo = (getContactInfo, hasContactInfo) => ({ getContactInfo, hasContactInfo });
const contactInfoByType = {
email: buildContactInfo((profile) => profile.getEmail(), (profile) => profile.hasEmail()),
phone: buildContactInfo((profile) => profile.getPhone(), (profile) => profile.hasPhone())
};
Then, when calling:
const contactInfo = contactInfoByType[type];
if (!contactInfo) {
throw new Error(`No contact info matched type '${type}'`);
} else {
contactInfo.hasContactInfo(profile);
contactInfo.getContactInfo(profile);
}