React async API call inside .map() function from actions [duplicate] - javascript

This question already has answers here:
How to return many Promises and wait for them all before doing other stuff
(6 answers)
Closed 2 years ago.
I am new to React JS. In my application I am facing a situation where I need to call an API multiple times with different url, like apiurl.com/abc, apiurl.com/xyz. These abc and xyz are stored in an array. Hence I wanted to use .map() to change the url to make multiple api calls. But in .map() async await wont work, so looking for some solutions if any. I have gone through few possible solution like with promises, but could not implement.
Here is my code:
export const someAction = () => async (dispatch, param) => {
let myArray = ["abc", "xyz"];
let id= "";
param1 = "someauthcode";
myArray.map((x) => {
id = x;
const myResponse = await loaders.myResponseApi(param1, id); *//This does not work as await should be in async call*
});
dispatch({ type: types.ARRAY_API, payload: myResponse });
}
So the idea is to make 2 api calls with apiurl.com/abc, apiurl.com/xyz. I have constructed the url (apiurl.com) in an different file.
Thanks in advance.

Turn your array into an array of promises and then use Promise.all
export const someAction = () => async(dispatch) => {
try {
const payload = await Promise.all(myArray.map(id => loaders.myResponseApi(param1,id)));
dispatch({type:types.ARRAY_API,payload});
} catch(err) {
// an error occurred in at least one of the promises
}
}

Instead of using .map() you can use a traditional for or while loop:
export const someAction = () => async (dispatch, param) => {
let myArray = ["abc", "xyz"];
let id= "";
param1 = "someauthcode";
let i = 0;
while (i < myArray.length) {
id = myArray[i];
const myResponse = await loaders.myResponseApi(param1, id);
// handle response here...
i++;
}
}

Related

Async fetch and trouble a copy array in class

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);
})

fetch constantly returns Promise {<pending>} [duplicate]

This question already has answers here:
How can I access the value of a promise?
(14 answers)
Async function returning promise, instead of value
(3 answers)
Closed 9 months ago.
I decided to create a special service aka ES6 class with needed functions to get data from API and then in index.js I could create an instance of the class and work with it. Unfortunately, when I try it, it always returns
Promise {<pending>}
and I don't really know what to do.
nytService.js:
export default class NYTService {
urlBase = "https://api.nytimes.com/svc/topstories/v2/";
category = "world";
apikey = *my api key* ;
async getNews(category = this.category) {
let url = `${this.urlBase}${category}.json?api-key=${this.apikey}`;
let res = await fetch(url)
let data = await res.json();
return data;
}
}
index.js:
import NYTService from "../services/nytService.js";
let nytService = new NYTService();
async function getNewsFinally() {
let res = await nytService.getNews();
return res;
}
console.log(getNewsFinally());
I did tried different things with the getNewsFinally function, did various .then chains, nothing helped
See if this tells you why:
async getNews(category = this.category) {
let url = `${this.urlBase}${category}.json?api-key=${this.apikey}`;
try {
let res = await fetch(url)
if (!res.ok) {
return res.statusText;
}
return await res.json();
} catch(err) {
console.error(err);
throw new Error(err);
}
}

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!

Passing intermediate data between promises in JS without using Promise.all and Promise.resolve [duplicate]

This question already has answers here:
How do I access previous promise results in a .then() chain?
(17 answers)
Closed 5 years ago.
I'm kind of a JS noob, but so far I really like the ES6 / React / Immutable capability to do functional programming and FRP, and in particular, the promise api. I particularly like the pattern of chaining .then's, eg, somePromiseGenerator().then(...).then(...).catch(...). This is perfect when the flow of asynchronous calls is perfectly linear. However, Often I want to pass results from the top of this chain to the end.
The sub-optimal pattern I've been using looks like:
somePromiseGenrator()
.then(r => {
const inter = makeSomeIntermediateResults(r);
const newPromise = makeNewPromise(r);
return Promise.all([newPromise, Promise.resolve(inter)]);
})
.then(r => {
handleR0andR1(r[0], r[1]);
})
this happens, for instance, when i get data from ElasticSearch and want to supplement it with stuff that's in SQL or Neo4J or call out to a secondary API.
Using Promise.all and especially Promise.resolve seems like a waste of effort. Is there a better way to do this work?
Using array destructuring to name variables:
somePromiseGenrator()
.then(r => {
const inter = makeSomeIntermediateResults(r);
const newPromise = makeNewPromise(r);
return Promise.all([newPromise, inter]);
})
.then([newPromiseResult, intermediateResult]) => {
handleR0andR1(newPromiseResult, intermediateResult);
})
That alone is much clearer to follow, and I also removed the redundant Promise.resolve(), as non-promise values in the array passed to Promise.all() will automatically be wrapped in that already.
Using async / await:
Assuming your version of Node.js is >= 7.6 (or you're using a transpiler / bundler / polyfill that supports ES2016 features), you can convert the following:
function promiseChain() {
return somePromiseGenrator()
.then(r => {
const inter = makeSomeIntermediateResults(r);
const newPromise = makeNewPromise(r);
return Promise.all([newPromise, inter]);
})
.then([newPromiseResult, intermediateResult]) => {
return handleR0andR1(newPromiseResult, intermediateResult);
})
}
into this:
async function promiseChain() {
const r = await somePromiseGenerator();
// I'm assuming this isn't a promise
// since it was initially wrapped in Promise.resolve()
const inter = makeSomeIntermediateResults(r);
const newPromiseResult = await makeNewPromise(r);
return handleR0andR1(newPromiseResult, inter);
}
You could define inter as a variable that has an extended scope, e.g. make it a parameter in an immediately invoked function expression:
(inter => somePromiseGenerator()
.then(r => {
inter = makeSomeIntermediateResults(r);
return makeNewPromise(r);
})
.then(r => handleR0andR1(r, inter))
)();
See also the options offered in this accepted answer, of which mine is a variant on option 2.
I guess you can do something like this if you don't prefer using Promise.all:
function pack(){
var args = arguments;
if((args.length - 1) % 2 != 0){
throw "Invalid input";
}
var obj = args[0],
promises = [];
for(var i = 1; i < args.length; i+=2){
let promise = args[i],
name = args[i+1];
promises.push(promise.then(val => {
obj[name] = val;
}));
}
return Promise.all(promises).then(() => obj);
}
Then you can use it this way:
Promise
.resolve({})
.then(obj => {
return pack(obj, genVal("a"), "a", genVal("b"), "b");
})
.then(obj => {
return pack(obj, genVal("c"), "c");
})
.then(console.log);
/* {
partA: "some value from part A",
partB: "some value from part B",
partC: "some value from part C"
} */
https://jsfiddle.net/DerekL/ssr23mjy/

async await in forEach [duplicate]

This question already has answers here:
Using async/await with a forEach loop
(33 answers)
Closed 6 years ago.
I have the following function that works as expected:
createObjectFrom(record) {
let obj = {};
this.opts.transformers.forEach((transformer, index) => {
const headerIndex = findIndex(this.headers, (header) => {
return header === transformer.column;
});
const value = transformer.formatter(record[headerIndex]);
obj[transformer.field] = value;
});
return obj;
}
I want to refactor it to use async await and call an async function in the body of the forEach like this:
createObjectFrom(record) {
let obj = {};
this.opts.transformers.forEach(async (transformer, index) => {
const headerIndex = findIndex(this.headers, (header) => {
return header === transformer.column;
});
const result = await this.knex('managers').select('name')
console.log(result);
const value = transformer.formatter(record[headerIndex]);
obj[transformer.field] = value;
});
return obj;
}
This will obviously break the function as the forEach is now executing asynchronously and the function will just execute and leave.
Is there a way I can use async await for the forEach to execute in a synchronous manner. Could I refactor to generators?
You can not enforce a normal JS function to wait on async behavior. There is no way!
So you would have to refactor your createObjectFrom to be async as well. And then probably go for map/reduce instead of forEach.
To be performant you don't want to do this:
for(transformer of this.opts.transformers) {
await this.knex('managers').select('name');
}
Instead you should use await Promise.all(...).
However in your case the call to knex seems not to depend on the transformer, so you can do this:
async createObjectFrom(record) {
let obj = {};
const result = await this.knex('managers').select('name')
this.opts.transformers.forEach(async (transformer, index) => {
const headerIndex = findIndex(this.headers, (header) => {
return header === transformer.column;
});
console.log(result);
const value = transformer.formatter(record[headerIndex]);
obj[transformer.field] = value;
});
return obj;
}
However if you want to do something like fetch for each item something async do it like this:
async foo(data) {
const subDataArr = await Promise.all(data.map(record => loadSubData(record)));
return subDataArr;
}

Categories