Async fetch and trouble a copy array in class - javascript

I have a problem... thant's a code:
class Currency {
cosnstructor() {
this.currencyInfo = [];
}
getCurrency(getInfo) {
this.currencyInfo = getInfo;
}
}
const actuallyCurrency = new Currency;
(async () => {
const response = await fetch(`http://api.nbp.pl/api/exchangerates/tables/A`);
const data = await response.json();
const currency = data[0].rates;
currency.map(element => curArr.push(element));
})();
const curArr = [];
actuallyCurrency.getCurrency(curArr);
this code working good, but I need in this.currencyInfo a new array, not reference to array curArr.

I this this is what you want:
class Currency {
constructor() {
this.currencyInfo = [];
}
getCurrency(getInfo) {
this.currencyInfo = [...getInfo]; // <-- change this line
}
}
const actuallyCurrency = new Currency;
(async () => {
const response = { json: () => { return [{rates:{a:1, b:2, c:3}}];}};
// const response = await fetch(`http://api.nbp.pl/api/exchangerates/tables/A`);
const data = await response.json();
const currency = data[0].rates;
for(key in currency) curArr.push(currency[key]);
actuallyCurrency.getCurrency(curArr);
console.log(actuallyCurrency.currencyInfo);
})();
const curArr = [];
Some thing for you to understand:
1-... is an operator that does a shallow copy of it's argument. So using as above you'll get a new array in currencyInfo.
2-Why actuallyCurrency.getCurrency(curArr); console.log(actuallyCurrency.currencyInfo); have to be inside the function ?
because os the async nature of the operation. Asyncs are postponed to when the execution has finished so the execution arrives in actuallyCurrency.getCurrency(curArr) BEFORE curArr is populated. This makes the internal currencyInfo array being null and not being populated again after execution.
3-Why this currency.map(element => curArr.push(element)); doesn't work ?
Because currency is an object, not an iterable array. If you want to iterate the elements of an object you have to options: get it's keys as an array, iterate this array and then get the value using it's key OR using for...in as I did.
Hope this is enough. Fell free to ask any question you'd like

There are a few improvements to be made. Probably the most important is arranging to check the currencyInfo instance variable after the fetch completes. This and other suggestions indicated by comments...
class Currency {
cosnstructor() {
this.currencyInfo = [];
}
// methods that assign (and don't return anything) ought to be called "set" something
setCurrency(array) {
this.currencyInfo = array;
}
// it probably makes sense to have this class do it's own async initialization
async fetchCurrency() {
const url = `http://api.nbp.pl/api/exchangerates/tables/A`;
// try/catch, so we can respond to failures
try {
const response = await fetch(url);
const data = await response.json();
// no need to map and not sure why the array needs to be copied. I suspect
// it doesn't but [...array] copies array
this.setCurrency([...data[0].rates]);
} catch (error) {
console.log('error fetching', error);
}
}
}
// instantiation requires ()
const actuallyCurrency = new Currency();
// no async/await at the top level
actuallyCurrency.fetchCurrency().then(() => {
console.log(actuallyCurrency.currencyInfo);
})

Related

Dynamically add values from different json responses - JS

I am calling an API and getting the total_tweet_count. If the meta tag contains a next_token, I fetch the next API (here I call the corresponding object) with the next_token and push the total_tweet_count to an array. However, if the response doesnt contain a next_token, I stop iterating and just push the total_tweet_count and return the array. For some reason, the code doesnt seem to run. Please help.
The below example should get the next_token from res1 and call res2 and push the tweet_count into the array. Then using the next_token of res2, fetch res3 data and push the count. Since res3 doesnt have a next_token, we end the loop and return the data. This is the expectation. Please advice.
const getData = async () => {
const totalCount = [];
const results = await Promise.resolve(data.res1);
totalCount.push(results.meta.total_tweet_count)
do {
const results1 = await Promise.resolve(data[`res${results.meta.next_token}`]);
totalCount.push(results1.meta.total_tweet_count)
}
while (results.meta.next_token)
return totalCount;
}
getData().then(res => console.log(res))
The final output I expect is [1,20, 8]
Please advice.
Fiddle Link: https://jsfiddle.net/czmwtj3e/
Your loop condition is constant:
const results = await Promise.resolve(data.res1);
//^^^^^^^^^^^^^
…
do {
…
}
while (results.meta.next_token)
What you are looking for is
const getData = async () => {
const totalCount = [];
let results = await Promise.resolve(data.res1);
totalCount.push(results.meta.total_tweet_count)
while (results.meta.next_token) {
results = await Promise.resolve(data[`res${results.meta.next_token}`]);
totalCount.push(results.meta.total_tweet_count)
}
return totalCount;
}
or
const getData = async () => {
const totalCount = [];
let results = {meta: {next_token: 1}};
do {
results = await Promise.resolve(data[`res${results.meta.next_token}`]);
totalCount.push(results.meta.total_tweet_count)
}
while (results.meta.next_token)
return totalCount;
}
Notice there is no const results1 in either of these, but an assignment to the mutable results variable.
As far as I can tell:
getData() is a wholly synchronous process acting on data therefore does not need to be an asyncFunction.
the required array of tweet counts can be created with a combination of Object.keys() and Array.prototype.reduce().
the .next_token property does not need to be used (unless it has some secret meaning beyond what has been explained in the question).
const getData = () => {
const keys = Object.keys(data); // array ['res1', 'res2', 'res3']
const totalCounts = keys.reduce((arr, key) => { // iterate the `keys` array with Array.prototype.reduce()
arr.push(data[key].meta.total_tweet_count); // push next tweet count onto the array
return arr; // return `arr` to be used in the next iteration
}, []); // start the reduction with empty array
return totalCounts; // [1, 20, 8]
};
In practice, you would:
pass data to the function
avoid intermediate variables
const getData = (data) => {
return Object.keys(data).reduce((arr, key) => {
arr.push(data[key].meta.total_tweet_count);
return arr;
}, []);
};

Call Async/await function from non async and add value returns undefined

I'm new in async function. I'm trying to apply it for UrlFetchApp.fetch.When there is a network problem, it throws an undefined error because there is no response code available. Therefore I gave a try to async function.
var addStamp = obj => {
obj.date = new Date();
return obj;
}
var decideWho = (form) => {
var choice = form.choice;
var obj = { key1 : 'func1(form.input1)',
key2 : 'func2(form.input2)'......};
return addStamp(eval(obj[choice]||obj[default]));
}
const fetchUrl = async (url, params) => {
let data = await UrlFetchApp.fetch(url,params).getContentText();
return JSON.parse(data);
}
var func1 = async (val) => {
var params = {...};
let response = await fetchUrl(val, params);
return response["message"];
}
var func2 = async (val) => {
var params = {...};
let response = await UrlFetch.app(val, params);
if (response.getResponseCode() === 200) {
var result = {step:"file", result:response};
  } else var result = {step: null, result: ""};
return result;
}
when func1 is called through decideWho, it returns result and addStamp is performed so date is added to the return value.
But when func2 is called, it returns value correctly but from decideWho, addStamp is not performed before it return so 'date' is undefined. In this incident, I have to call addStamp within the func2 while returning the value.
How to avoid this undefined?
I'm sure I'm messing up with async/await.
I tried to put async/await in decideWho by declaring extra value with await then wrap it with addStamp then return it.
let result = await eval(obj[choice]||obj[default]);
return addStamp(result);
}
However when decideWho is called from clientside with google.script.run, it throws undefined error. I guess somewhere I need to add await one more time but I can't find out where.
Couple of points worth to mention
(*Because the form from client side calls various function, I decided to put bunch into one bucket. i.e. regardless of the option, it will call only decideWho of server function from the client, and through option's value, it will choose which function will be called with parameters. And each function returns value and addStamp from decideWho so that I don't have to repeat this process/code in each and every function.
**Only func1 and func2 are async but not others.)
This worked
const addStamp = async (obj) => {
const temp = await obj;
temp.date = new Date();
return temp;
}
Or shorter version
const addStamp = async (obj) => (
{ ...(await obj),
date: new Date()})
Thanks to Josh and Andy
I think this should work for you. It has several other refactors in it too...
The basic issue looks to me that you are not awaiting your async functions when you add them to the object.
// use const - variables are unreliable!
// Rest Spread syntax
const addStamp = obj => ({
...obj,
date: new Date();
})
// this can throw on a network or JSON parse error,
// the fn that calls (and awaits / .then chains)
// on decideWho needs to handle that.
const decideWho = async form => {
const { choice } = form;
// you need to await these, they are async
const obj = {
key1 : await func1(form.input1),
key2 : await func2(form.input2)
};
return addStamp(eval(obj[choice]||obj[default]));
}
// can throw from network or JSON parse
const fetchUrl = async (url, params) => {
let data = await UrlFetchApp.fetch(url,params).getContentText();
return JSON.parse(data);
}
const func1 = async val => {
const params = {...};
let response = await fetchUrl(val, params);
return response["message"];
}
const func2 = async val => {
const params = {...};
const response = await UrlFetch.app(val, params);
// use a ternary, and no intermediate var
return (response.getResponseCode() === 200) ?
{step:"file", result:response} :
{step: null, result: ""};
}
}

Using Async Values in another function (JavaScript)

I am currently working with destructuring arrays in Javascript, I would like to access these variables in other functions but currently, I am struggling to figure out how I might go about this.
I've tried calling the function and then console.log(thermostatArray) -> I believe this returned pending promise
I've tried calling the function and awaiting it and then console.log thermostatArray.
dataFormat() is properly able to see log and use the array but heatCallCheck() is not and I am not seeing past the issue yet.
var express = require("express");
var router = express.Router();
const multer = require("multer");
var Excel = require("exceljs");
const index = require("../routes/index");
const workbook = new Excel.Workbook();
async function convertFile(workbook) {
csvWorkbook = workbook.csv.readFile("./uploads/uploadedFile.csv");
await csvWorkbook.then(async function(csvWorkbook) {
const worksheet = workbook.getWorksheet("sheet1");
try {
// await dataFormat(worksheet);
await heatCallCheck(worksheet,)
} catch (err) {
console.log(err);
}
await workbook.xlsx.writeFile("./uploads/convertedFile.xlsx").then(() => {
console.log("converted file written");
});
});
}
async function dataFormat(worksheet) {
let thermostatArray = []
await csvWorkbook.then(async function(worksheet) {
const serialNum = worksheet.getCell("D1").value;
const thermostatName = worksheet.getCell("D2").value;
const startDate = worksheet.getCell("D3").value;
const endDate = worksheet.getCell("D4").value;
const thermostat = worksheet.eachRow({includeEmpty: true}, function(row,rowNumber){
if (rowNumber > 6) {
thermostatArray.push(row.values)
}
})
console.log(`${thermostatArray[5]} Array Sample from dataFormat` )
console.log(`${thermostatArray[6]} Array Sample from dataFormat` )
return thermostatArray
})}
async function heatCallCheck(worksheet,thermostatArray) {
let test = await dataFormat(worksheet).then(thermostatArray => {
return thermostatArray[5]
}).catch(err => {
console.error(err)
})
console.log(`${test} result `)
}
My expected results, in this case, would be that I would be able to see the 4th element in thermostat array using the heatCallCheck() function.
I figured I would be able to access it after the .then is called.
my understanding is that .then(thermostatArray =>
makes that array the return value.
You do this:
async function someFunction() {
const myResultFromAPromise = await functionThatReturnsAPromise();
// ....do some stuff
return myResultFromAPromise;
}
OR
function someFunction() {
return functionThatReturnsAPromise().then(function(myResultFromAPromise) {
// ...do some stuff
return myResultFromAPromise;
});
}
but don't do both, it's just terribly confusing.
EDIT: as a commenter pointed out, you can await anything, but it's clear from your code that you're very confused about the point of async/await

Waiting for all firebase call to finish with map function

I'm fetching my user data and the map function is called several times for each user. I want to wait until all data was pushed to the array and then manipulate the data. I tried using Promise.all() but it didn't work.
How can I wait for this map function to finish before continuing?
Needless to say that the array user_list_temp is empty when printed inside the Promise.all().
const phone_list_promise_1 = await arrWithKeys.map(async (users,i) => {
return firebase.database().ref(`/users/${users}`)
.on('value', snapshot => {
user_list_temp.push(snapshot.val());
console.log(snapshot.val());
})
}
);
Promise.all(phone_list_promise_1).then( () => console.log(user_list_temp) )
I changed the code to this but I still get a wrong output
Promise.all(arrWithKeys.map(async (users,i) => {
const eventRef = db.ref(`users/${users}`);
await eventRef.on('value', snapshot => {
const value = snapshot.val();
console.log(value);
phone_user_list[0][users].name = value.name;
phone_user_list[0][users].photo = value.photo;
})
console.log(phone_user_list[0]);
user_list_temp.push(phone_user_list[0]);
}
));
console.log(user_list_temp); //empty array
}
It is possible to use async/await with firebase
This is how I usually make a Promise.all
const db = firebase.database();
let user_list_temp = await Promise.all(arrWithKeys.map(async (users,i) => {
const eventRef = db.ref(`users/${users}`);
const snapshot = await eventref.once('value');
const value = snapshot.value();
return value;
})
);
This article gives a fairly good explanation of using Promise.all with async/await https://www.taniarascia.com/promise-all-with-async-await/
Here is how I would refactor your new code snippet so that you are not mixing promises and async/await
let user_list_temp = await Promise.all(arrWithKeys.map(async (users,i) => {
const eventRef = db.ref(`users/${users}`);
const snapshot= await eventRef.once('value');
const value = snapshot.val();
console.log(value);
phone_user_list[0][users].name = value.name; // should this be hardcoded as 0?
phone_user_list[0][users].photo = value.photo; // should this be hardcoded as 0?
console.log(phone_user_list[0]);
return phone_user_list[0]; // should this be hardcoded as 0?
})
);
console.log(user_list_temp);
Here is a simple example that uses fetch, instead of firebase:
async componentDidMount () {
let urls = [
'https://jsonplaceholder.typicode.com/todos/1',
'https://jsonplaceholder.typicode.com/todos/2',
'https://jsonplaceholder.typicode.com/todos/3',
'https://jsonplaceholder.typicode.com/todos/4'
];
let results = await Promise.all(urls.map(async url => {
const response = await fetch(url);
const json = await response.json();
return json;
}));
alert(JSON.stringify(results))
}
If I understand your question correctly, you might consider revising your code to use a regular for..of loop, with a nested promise per user that resolves when the snapshot/value for that user is available as shown:
const user_list_temp = [];
/*
Use regular for..of loop to iterate values
*/
for(const user of arrWithKeys) {
/*
Use await on a new promise object for this user that
resolves with snapshot value when value recieved for
user
*/
const user_list_item = await (new Promise((resolve) => {
firebase.database()
.ref(`/users/${users}`)
.on('value', snapshot => {
/*
When value recieved, resolve the promise for
this user with val()
*/
resolve(snapshot.val());
});
}));
/*
Add value for this user to the resulting user_list_item
*/
user_list_temp.push(user_list_item);
}
console.log(user_list_temp);
This code assumes that the enclosing function is defined as an asynchronous method with the async keyword, seeing that the await keyword is used in the for..of loop. Hope that helps!

Lodash: is it possible to use map with async functions?

Consider this code
const response = await fetch('<my url>');
const responseJson = await response.json();
responseJson = _.sortBy(responseJson, "number");
responseJson[0] = await addEnabledProperty(responseJson[0]);
What addEnabledProperty does is to extend the object adding an enabled property, but this is not important. The function itself works well
async function addEnabledProperty (channel){
const channelId = channel.id;
const stored_status = await AsyncStorage.getItem(`ChannelIsEnabled:${channelId}`);
let boolean_status = false;
if (stored_status == null) {
boolean_status = true;
} else {
boolean_status = (stored_status == 'true');
}
return _.extend({}, channel, { enabled: boolean_status });
}
Is there a way to use _.map (or another system), to loop trough entire responseJson array to use addEnabledProperty against each element?
I tried:
responseJson = _.map(responseJson, function(channel) {
return addEnabledProperty(channell);
});
But it's not using async so it freeze the app.
I tried:
responseJson = _.map(responseJson, function(channel) {
return await addEnabledProperty(chanell);
});
But i got a js error (about the row return await addEnabledProperty(chanell);)
await is a reserved word
Then tried
responseJson = _.map(responseJson, async function(channel) {
return await addEnabledProperty(channell);
});
But I got an array of Promises... and I don't understand why...
What else!??
EDIT: I understand your complains about I didn't specify that addEnabledProperty() returns a Promise, but, really, I didn't know it. In fact, I wrote "I got an array of Promises... and I don't understand why "
To process your response jsons in parallel you may use Promise.all:
const responseJson = await response.json();
responseJson = _.sortBy(responseJson, "number");
let result = await Promise.all(_.map(responseJson, async (json) =>
await addEnabledProperty(json))
);
Since addEnabledProperty method is async, the following also should work (per #CRice):
let result = await Promise.all(_.map(responseJson, addEnabledProperty));
I found that I didn't have to put the async / await inside of the Promise.all wrapper.
Using that knowledge, in conjunction with lodash chain (_.chain) could result in the following simplified version of the accepted answer:
const responseJson = await Promise.all( _
.chain( response.json() )
.sortBy( 'number' )
.map( json => addEnabledProperty( json ) )
.value()
)
How about using partial.js(https://github.com/marpple/partial.js)
It cover both promise and normal pattern by same code.
_p.map([1, 2, 3], async (v) => await promiseFunction());
You can use Promise.all() to run all the promises in your array.
responseJson = await Promise.all(_.map(responseJson, (channel) => {
return addEnabledProperty(channel);
}));
If you want to iterate over some object, you can use keys first to get an array of keys and then just loop over your keys while awaiting for necessary actions.
This works when you want to wait until every previous iteration step is finished before getting into the next one.
Here is a consolidated asyncMap function that you can use for your object:
async function asyncMap(obj, cb) {
const result = {};
const keysArr = keys(obj);
let keysArrLength = keysArr.length;
while (keysArrLength-- > 0) {
const key = keysArr[keysArrLength];
const item = obj[key];
// eslint-disable-next-line no-await-in-loop
result[key] = await cb(item, key);
}
return result;
}
And then, for your example case:
responseJson = await asyncMap(responseJson, addEnabledProperty);
Otherwise use Promise.all like was proposed above to run all the iteration steps in parallel

Categories