Modify external object in promise function - javascript

I have an array of objects (books) that are missing some fields, so I'm using node-isbn module to fetch the missing data. However, I'm having trouble persisting the updates to the objects. Here's some example code:
const isbn = require('node-isbn');
var books = []; // assume this is filled in elsewhere
books.forEach(function(item) {
if (item.publication_year == '' ||
item.num_pages == '') {
isbn.provider(['openlibrary', 'google'])
.resolve(item.isbn)
.then(function (book) {
item.num_pages = book.pageCount;
item.publication_year = book.publishedDate.replace( /^\D+/g, '');
}).catch(function (err) {
console.log('Book not found', err);
});
}
console.log(item)
});
However, the console log shows that the num_pages and publication_year fields are still empty. How can I fix this?

Try not to use Promise inside forEach

Put your console.log inside the then block , it will print the result for you.You are doing a asynchronous operation by resolving promise so it will take some time for the data to come back , however since your console.log is outside of that promise , that will get executed first.
So if you want to see those values, you should put your console.log inside your then block.
However, You can use await and for of syntax to achieve the result
for await (item of books) {
if (item.publication_year == '' || item.num_pages == '') {
const book = await isbn.provider(['openlibrary', 'google'])
.resolve(item.isbn);
item.num_pages = book.pageCount;
item.publication_year = book.publishedDate.replace( /^\D+/g,'');
}
console.log(item)
}
console.log(books)

Related

Function returning blank array in inspect element with inaccessible data inside

SCENARIO
So I am trying to do multiple API requests with different URLs, add all the responses to an array, then return the array and use it. My code:
const getData = (dataURLs) => {
let returnData = [];
for (let i = 0; i < dataURLs.length; i++) {
getFetch(dataURLs[i]).then((response) => {
returnData.push(response);
return response;
});
}
console.log(returnData[0]);
return returnData;
};
So this is the function that makes the requests, here is what the getFetch function is:
const getFetch = async (url) => {
return fetch(url)
.then((response) => {
if (!response.ok) {
// get error message from body or default to response status
const error = (response && response.message) || response.status;
return error;
}
return response.json();
})
.catch((error) => {
return error;
});
};
This just makes the request and returns the JSON which is what I want, and this function works as I use it in other places.
PROBLEM
My issue is, when i make the request using the getData function, it will return a blank array '[]', however when I click on this array in inspect element, it displays this.
[] ->
0: {nodes: Array(5), edges: Array(5), self: Array(1)}
length: 1
If I try to access anything in this array in js it just doesn't let me. But if I look at it in Inspect Element, it will be a blank array that I can expand and it displays the requested data inside it
Just wondering if anyone knew a fix to this?
Thanks :)
The issue you're running into here is that the function returns the array before the promises have resolved (and put the data that you want into the arrays). You will need to wait for the promises first. There are a few ways you can do this, but one way is to put the promises into the array and use a Promise.all() to get all of the values when they are available.
const getData = (dataURLs) => {
let returnPromises = [];
for (let i = 0; i < dataURLs.length; i++) {
returnPromises.push(getFetch(dataURLs[i]));
}
return Promise.all(returnPromises);
};
From here on you will continue to use this function's result as a promise.
getData([...]).then(([result1, result2, ...resultN]) => {...})

I cant get the result out of a fulfilled Promise React

Hello everyone
My goal:
Get the result out of a Promise that is fullfilled to use the result in a other piece of code
the results that I get are:
Results
but the expected result is the [[PromiseResult]].
I tried other methods I found on here. but there was one problem I did get the result only to show up in the command.log but when I tried to assign it to a value it looked like it skipped right over the code. I know this sounds like a duplicate posts of a post of How to return the response from an asynchronous call? but I tried it and it didn't work for me.
the check boundries returns a Promise
function getData() {
setLoading(true);
ref.onSnapshot((querySnapshot) => {
const items = [];
querySnapshot.forEach((doc) => {
let itemurl = 'http://leafletjs.com/examples/custom-icons/leaf-green.png';
let item = doc.data()
console.log(checkBoundaries(doc.data().RawData))
itemurl = checkBoundaries(doc.data().RawData)
console.log( itemurl)
item.colorIcon = itemurl;
console.log(item)
items.push(item);
console.log(items)
});
setRuuviTag(items);
setLoading(false);
});
}
but i cant get item.colorIcon to become the promise result
it would help alot if anyone knew a fix for this
Update
async function checkBoundaries(rdata) {
let iconurl;
try {
const response = await PostRawData(rdata)
const data = response.data
if (22.00>data.temperature && data.temperature > 4.00) {
console.log('if')
iconurl = 'https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-blue.png'
}
else {
console.log('else')
iconurl = 'https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-red.png'
}
} catch (err) {
console.error(err)
}
console.log(`this is the url ${iconurl}`)
return iconurl
// ...
}
I will now get the things i tried but wont work

Pushing elements into the array works only inside the loop

I got some data which I'm calling from API and I am using axios for that. When data is retrieved, I dump it inside of a function called "RefractorData()" just to organize it a bit, then I push it onto existing array. The problems is, my array gets populated inside forEach and I can console.log my data there, but once I exit the loop, my array is empty.
let matches: any = new Array();
const player = new Player();
data.forEach(
async (match: any) => {
try {
const result = await API.httpRequest(
`https://APILink.com/matches/${match.id}`,
false
);
if (!result) console.log("No match info");
const refractored = player.RefractorMatch(result.data);
matches.push({ match: refractored });
console.log(matches);
} catch (err) {
throw err;
}
}
);
console.log(matches);
Now the first console.log inside forEach is displaying data properly, second one after forEach shows empty array.
Managed to do it with Promise.all() and Array.prototype.map()
.
const player = new Player();
const matches = result.data;
const promises = matches.map(async (match: any) => {
const response: any = await API.httpRequest(
`https://API/matches/${match.id}`,
false
);
let data = response.data;
return {
data: player.RefractorMatch(data)
};
});
const response: any = await Promise.all(promises);
You must understand that async functions almost always run later, because they deppend on some external input like a http response, so, the second console.log is running before the first.
There a few ways to solve this. The ugliest but easiest to figure out is to create a external promise that you will resolve once all http requests are done.
let matches = [];
let promise = new Promise((resolve) => {
let complete = 0;
data.forEach((match: any) => {
API.httpRequest(...).then((result) => {
// Your logic here
matches.push(yourLogicResult);
complete++;
if (complete === data.length) {
resolve();
}
}
}
};
console.log(matches); // still logs empty array
promise.then(() => console.log(matches)); // now logs the right array
You can solve this using other methods, for example Promise.all().
One very helpful way to solve it is using RxJs Observables. See https://www.learnrxjs.io/
Hope I helped you!

define outer scope variable inside function

i am building validation for one of form's field serverside (expressjs) and doing following actions for that:
Read data from json file
Get property from it (Array)
Check if it contains every single element of user generated array and nothing more, for example:
[1,2,3,4,5]; (json array)
[1,2,3,4,5,6] (user generated array) //must return false
[1,2,3,4,5];
[1,3,4] //must return true;
[1,2,3,4,5];
[1,2,7] //must return false;
so i am using this code for that:
const contains = (arr1, arr2) => {
arr2.every(v => arr1.indexOf(v) !== -1)
}
var match;
fs.readFile('../tags.json', 'utf8', (err, data)=>{
var JsonData = JSON.parse(data);
var tagsArray = JsonData.tags;
console.log(tagsArray)
console.log(tags)
if(tagsArray instanceof Array){
console.log('tagsArray is array')
}
if(!contains(tagsArray, tags)){
match = false
}
else{
match = true
}
console.log(match + ' blah1')
});
console.log(match + ' blah2')
if(match == false){
return res.status(409).send({
message: 'Do not provide your own tags'
});
}
but it always returns false inside fs.readFile block because it returns undefined outside fs.readFile block, so this means that contains function return undefined (i tested it)
so what is the clue for this?
Thanks!
fs.readFile is asynchronous, so any code that depends on its result (the file being read) needs to go within your callback function. (The callback function is the (err, data) => { ... } part.)
Move the console.log(match + 'blah2') and if(match == false) { ... } parts inside of the callback (after the blah1 line).
You could also look into async or use fs.readFileSync which would allow you to avoid using callback functions.
Another side point, you will want to make sure you always reach a res.send() line, i.e. when match == true in your case. Otherwise your http request will not return when match is true.
Edit:
Here's a really basic structure for express, mostly pseudocode & comments, just to illustrate callbacks:
app.post('/tags', (req, res) => {
// your setup code here
fs.readFile('../tags.json', 'utf8', (err, data) => {
console.log('readFile has finished')
// now you have heard back from readFile
// check the err and send 500 if there was a problem
// otherwise work with the file in the var data
// any other db-related stuff also goes in here, which probably
// has its own callback you need to use
db.save(data, (err) => {
// db call is done, potentially with an error
// Here you can use `res` to send http response
})
// !! again here the db is still doing its work
})
// !! anything you add here will be executed before readFile is done
console.log('readFile is probably still at work')
})
I should also point out that you want contains to return the bool value, i.e. return arr2.every(...)
You can use async/await :
async function read(){
let data = await fs.readFile(<path>);
console.log(data); //You can use data everywhere within this scope
}

Returning empty array in NodeJs using mongoose

I am trying to fill an array with records from a mongoDB database using mongoose. When I am trying to fill the records. It shows an empty array outside the function even though I am declaring the outside the function. Below is the code.
var saveMessageToVariable = function(){
var records = [];
var spark_ids = [];
var obj = new Object();
Message.find().distinct("spark_id").exec(function(err,data) {
data.forEach(function (id) {
if(id != null)
spark_ids.push(id);
});
// console.log(spark_ids.length);
spark_ids.forEach(function(spark_id){
Message.findOne({"spark_id":spark_id}).sort({"date":-1}).exec(function(err,data){
obj.spark_id = data.spark_id;
obj.meesage = data.message;
obj.date = data.date;
obj.message_id = data._id;
records.push(obj);
});
});
});
console.log(records);
}
When I run this, the log is showing an empty array. How do I resolve this issue?
It's an asynchronous call and as soon as data is fetched from database control shifts to next line and therefore prints the initial value, I would prefer you to use a callback like this:
function(spark_id,callback){
Message.findOne({"spark_id":spark_id}).sort({"date":-1}).exec(function(err,data){
obj.spark_id = data.spark_id;
obj.meesage = data.message;
obj.date = data.date;
obj.message_id = data._id;
callback(obj);
});
}
function(obj)
{
records.push(obj);
}
You two other approachs for this:
1) use try and catch block.
2) use async and await keyword.
Cheers!
I don't have much experience with moongoose, but according to the docs it supports promises since Version 4.
Then this should work:
//I assume you'll need this more often
function notNull(value){ return value != null; }
//returns a promise of the records-Array
var saveMessageToVariable = function(){
//returns a promise of a formated message
function getMessage( spark_id ){
return Message.findOne({ spark_id })
.sort({ date: -1 })
//.exec()
.then( formatMessage )
}
function formatMessage( msg ){
return {
spark_id: msg.spark_id,
message: msg.message,
date: msg.date,
message_id: msg._id
}
}
return Message.find()
.distinct("spark_id")
//.exec()
.then(function( ids ){
//waits for all findOnes to complete, then returns an Array
return Promise.all(
ids.filter( notNull ).map( getMessage )
));
}
I'm not sure, wether you need exec() in this code or not. You should check that.
//usage
saveMessageToVariable.then(function(records){
console.log(records);
})
btw. saveMessageToVariable doesn't reflect at all what this function does. You should choose a better name.

Categories

Resources