How to populate without using mongoose - javascript

I'm trying to implement populate() function without using mongoose in the code below:
`
course.students.forEach(async (student, i) => {
const s = await Student.findById(student._id);
console.log(s.toObject()); // gets student data properly
course.students[i] = s; // does not work
});
console.log(course.json());
`
I just want to update the students array with the data fetched. Can anyone explain me with the assignation doesn't works?
Thank you all! :)

forEach is not meant to be used with await, try to change to a for loop and use lean() to return a plain object from the query:
for (let i = 0; i < course.students.length; i++) {
const student = course.students[i];
course.students[i] = await Student.findById(student._id).lean();
}
console.log(course);

Related

async functions not executing in the correct order inside a map function

I have created an async function that will extra the data from the argument, create a Postgres query based on a data, then did some processing using the retrieved query data. Yet, when I call this function inside a map function, it seemed like it has looped through all the element to extra the data from the argument first before it proceed to the second and the third part, which lead to wrong computation on the second element and onwards(the first element is always correct). I am new to async function, can someone please take at the below code? Thanks!
async function testWeightedScore(test, examData) {
var grade = [];
const testID = examData[test.name];
console.log(testID);
var res = await DefaultPostgresPool().query(
//postgres query based on the score constant
);
var result = res.rows;
for (var i = 0; i < result.length; i++) {
const score = result[i].score;
var weightScore = score * 20;
//more computation
const mid = { "testID": testID, "score": weightScore, more values...};
grade.push(mid);
}
return grade;
}
(async () => {
const examSession = [{"name": "Sally"},{"name": "Bob"},{"name": "Steph"}]
const examData = {
"Sally": 384258,
"Bob": 718239,
"Steph": 349285,
};
var test = [];
examSession.map(async sesion => {
var result = await testWeightedScore(sesion,examData);
let counts = result.reduce((prev, curr) => {
let count = prev.get(curr.testID) || 0;
prev.set(curr.testID, curr.score + count);
return prev;
}, new Map());
let reducedObjArr = [...counts].map(([testID, score]) => {
return {testID, score}
})
console.info(reducedObjArr);
}
);
})();
// The console log printed out all the tokenID first(loop through all the element in examSession ), before it printed out reducedObjArr for each element
The async/await behaviour is that the code pause at await, and do something else (async) until the result of await is provided.
So your code will launch a testWeightedScore, leave at the postgresql query (second await) and in the meantime go to the other entries in your map, log the id, then leave again at the query level.
I didn't read your function in detail however so I am unsure if your function is properly isolated or the order and completion of each call is important.
If you want each test to be fully done one after the other and not in 'parallel', you should do a for loop instead of a map.

Problems with RealmDB objects in React Native with Reactotron

I'm having trouble with RealmDB objects in a React Native project, every query I do the RealmDB returns array of empty objects {} in reactotron and if I try to use rest/spread operator. However, when I access
attribute by attribute works. Someone had the same issue and managed to solve?
P.S: I already linked realm and reactotron in the project
Images about what I described
Reactotron: https://i.stack.imgur.com/OhN1D.png
Code: https://i.stack.imgur.com/gsNwk.png
An example of a piece of code where I try to discover the problem:
async getAllMov(){
try{
const realm = await getRealm();
const listaMov = realm.objects(MovSchema.schema.name);
console.tron.log(listaMov[4]);
const d = {...listaMov[4]};
console.tron.log(d);
let dados = {
movID: listaMov[4].movID,
titulo: listaMov[4].titulo,
descricao: listaMov[4].descricao,
valor: listaMov[4].valor,
tipo: listaMov[4].tipo
};
console.tron.log(dados);
}catch(e){
console.tron.log(e.message);
}
}
This solved my problem, I hope that helps you also:
export default function getRealmObjects = async (schemaName) => {
const realm = await getRealm();
var objects = realm.objects(schemaName);
//this is because realm put some obstacles when you try to modify it's objects, the solution found was to clone the results before return
let result = [];
for (let i = 0; i < objects.length; i++) {
let item = {};
for (const key in objects[i]) {
item[key] = objects[i][key];
}
result.push(item)
}
return result?.length > 0 ? result : [];
}
Basically you cannot modify realm objects, so you have to clone it before use.

setState after loading an array from firebase

I saw similar questions online but none of their solutions worked for me.
I am building an app in React Native which loads information from firebase and then displays it. I want to load objects from firebase, put them in an array and then set the state so the class would re-render and display it once it's loaded.
The information is being loaded fine, but I can't find a way to call setState after the array has loaded. I tried promises and tried using another function as a callback, but nothing had worked for me yet. It always executes setState before the array is loaded. I don't know if using setTimeout in some way would be a good solution though.
Here is the some of the code (I want to update the jArray in this.state and then re-render the page) :
constructor(props){
super(props);
this.state = {
jArray: []
}
}
componentDidMount(){
this.getJ();
}
async getJ(){
let jArray = [];
let ref = database.ref('users/' + fb.auth().currentUser.uid + '/usersJ');
let snapshot = await ref.once('value');
let itemProcessed = 0;
let hhh = await snapshot.forEach(ch => {
database.ref('J/' + ch.val()).once('value')
.then(function(snapshot1){
jArray.push(snapshot1);
itemProcessed++;
console.log(itemProcessed);
if(snapshot.numChildren()===jArray.length){
JadArray = jArray
}
})
});
}
Thanks (:
Maybe you can do something like this:
// don't forget to use an arrow function to bind `this` to the component
getJ = async () => {
try {
const ref = database.ref('users/' + fb.auth().currentUser.uid + '/usersJ');
const snapshot = await ref.once('value');
// it might be easier just to start by getting the data into an object you can use like this
const dataObj = snapshot.val();
// extract the keys
const childKeys = Object.keys(dataObj);
// use the keys to create a function that makes an array of all the promises we want
const createPromises = () =>
childKeys.map(childKey => database.ref('J/' + childKey).once('value'));
// await ALL the promises before moving on
const jArray = await Promise.all(createPromises());
// now you can set state
this.setState({ jArray });
// remember to catch any errors
} catch (err) {
console.warn(err);
// you might want to do something else to handle this error...
}
};
}
So in the end I found a solution to my problem, from: https://stackoverflow.com/a/47130806/3235603
My code looks like this:
async getJ(){
let jArray = [];
let ref = database.ref('users/' + fb.auth().currentUser.uid + '/usersJ');
let snapshot = await ref.once('value');
let itemProcessed = 0;
let that = this;
let hhh = await snapshot.forEach(ch => {
database.ref('J/' + ch.val()).once('value')
.then(function(snapshot1){
jArray.push(snapshot1);
itemProcessed++;
console.log(itemProcessed);
if(snapshot.numChildren()===jArray.length){
JadArray = jArray
that.setState({
jadArray : jArray,
dataLoaded : true
},() => console.log(that.state))
}
})
});
}
It's kinda a tricky one with 'this' and 'that', but it all works fine now.

React Axios API call with array loop giving wrong order?

I was learning react and doing some axios api call with an array. I did a code on gathering data through coinmarketcap api to learn.
So, my intention was to get the prices from the api with a hardcoded array of cryptocurrency ids and push them into an array of prices. But I ran into a problem with the prices array, as the prices were all jumbled up. I was supposed to get an array in this order
[bitcoinprice, ethereumprice, stellarprice, rippleprice]
but when I ran it in the browser, the prices came randomly and not in this order, sometimes I got my order, sometimes it didn't. I used a button which onClick called the getPrice method. Does anyone know what went wrong with my code? Thanks!
constructor(){
super();
this.state = {
cryptos:["bitcoin","ethereum","stellar","ripple"],
prices:[]
};
this.getPrice = this.getPrice.bind(this);
}
getPrice(){
const cryptos = this.state.cryptos;
console.log(cryptos);
for (var i = 0; i < cryptos.length; i++){
const cryptoUrl = 'https://api.coinmarketcap.com/v1/ticker/' + cryptos[i];
axios.get(cryptoUrl)
.then((response) => {
const data = response.data[0];
console.log(data.price_usd);
this.state.prices.push(data.price_usd);
console.log(this.state.prices);
})
.catch((error) => {
console.log(error);
});
}
}
If you want to receive the data in the order of the asynchronous calls you make, you can use Promise.all, that waits until all the promises of an array get executed and are resolved, returning the values in the order they were executed.
const cryptos = ['bitcoin', 'ethereum', 'stellar', 'ripple'];
const arr = [];
for (var i = 0; i < cryptos.length; i++){
const cryptoUrl = 'https://api.coinmarketcap.com/v1/ticker/' + cryptos[i];
arr.push(axios.get(cryptoUrl));
}
Promise.all(arr).then((response) =>
response.map(res => console.log(res.data[0].name, res.data[0].price_usd))
).catch((err) => console.log(err));
You could use a closure in the for loop to capture the value of i and use it as the index once the data is returned rather than using push:
getPrice(){
const cryptos = this.state.cryptos;
console.log(cryptos);
for (var i = 0; i < cryptos.length; i++) {
const cryptoUrl = 'https://api.coinmarketcap.com/v1/ticker/' + cryptos[i];
(function (x) {
axios.get(cryptoUrl)
.then((response) => {
const data = response.data[0];
console.log(data.price_usd);
var newPrices = this.state.prices;
newPrices[x] = data.price_usd;
this.setState({prices: newPrices});
console.log(this.state.prices);
})
.catch((error) => {
console.log(error);
});
})(i);
}
}

NodeJS + TinyURL - adding items to a list

Sorry if this is a stupid question, but how would I go about adding items into a list? So what I've got is a loop that basically runs through and tries to convert all the urls to tinyurls from a web scraper . It still produces an empty list for images_short. I'm not very familiar with nodejs's syntax. Here's a snippet of code, I've put some data in the images_long list:
const TinyURL = require('tinyurl');
var images_long = ['https://hypebeast.imgix.net/http%3A%2F%2Fhypebeast.com%2Fimage%2F2017%2F06%2Fadidas-skateboarding-lucas-premiere-adv-primeknit-khaki-0.jpg?fit=max&fm=pjpg&h=344&ixlib=php-1.1.0&q=90&w=516&s=728297932403d74d2ac1afa5ecdfa97d', 'https://hypebeast.imgix.net/http%3A%2F%2Fhypebeast.com%2Fimage%2F2017%2F06%2Fadidas-nmd-r1-stlt-triple-black-first-look-0.jpg?fit=max&fm=pjpg&h=344&ixlib=php-1.1.0&q=90&w=516&s=918752eba81826e4398950efc69a5141'];
var images_short = [];
for (i = 0; i < 2; i++) {
TinyURL.shorten(images_long[i], function(res) {
images_short.push(res[i]);
});
}
I still get an empty list when I changed images_short.push(res[i]); to images_short.push(res);
res is a string, so just images_short.push(res); will do the trick. Also, you should iterate with respect to the length of the variable you're indexing, and you should var your indexing variable (i):
const TinyURL = require('tinyurl');
var images_long = [
'https://hypebeast.imgix.net/http%3A%2F%2Fhypebeast.com%2Fimage%2F2017%2F06%2Fadidas-skateboarding-lucas-premiere-adv-primeknit-khaki-0.jpg?fit=max&fm=pjpg&h=344&ixlib=php-1.1.0&q=90&w=516&s=728297932403d74d2ac1afa5ecdfa97d',
'https://hypebeast.imgix.net/http%3A%2F%2Fhypebeast.com%2Fimage%2F2017%2F06%2Fadidas-nmd-r1-stlt-triple-black-first-look-0.jpg?fit=max&fm=pjpg&h=344&ixlib=php-1.1.0&q=90&w=516&s=918752eba81826e4398950efc69a5141'];
var images_short = [];
for (var i = 0; i < images_long.length; i++) {
TinyURL.shorten(images_long[i], function(res) {
images_short.push(res);
});
}
The tinyurl library is async.
Is we use native map, the resulting callback wouldn't be returned if we try to console.log(images_short) until all the links in the array have been shortened.
We can however, use async and specificically use async.map to return the results like the example below.
const TinyURL = require('tinyurl');
const async = require('async');
var images_long = [
'https://hypebeast.imgix.net/http%3A%2F%2Fhypebeast.com%2Fimage%2F2017%2F06%2Fadidas-skateboarding-lucas-premiere-adv-primeknit-khaki-0.jpg?fit=max&fm=pjpg&h=344&ixlib=php-1.1.0&q=90&w=516&s=728297932403d74d2ac1afa5ecdfa97d',
'https://hypebeast.imgix.net/http%3A%2F%2Fhypebeast.com%2Fimage%2F2017%2F06%2Fadidas-nmd-r1-stlt-triple-black-first-look-0.jpg?fit=max&fm=pjpg&h=344&ixlib=php-1.1.0&q=90&w=516&s=918752eba81826e4398950efc69a5141'];
function shorten(item, cb) {
TinyURL.shorten(item, function(res) {
cb(null, res);
});
}
async.map(images_long, shorten, (err, results) => {
console.log(results);
});
we can assign images_short if you want to keep consistency.

Categories