Using async await when mapping over an arrays values - javascript

I am trying to use the new async/await in conjunction with mapping over an array of values.
However I am a bit confused, which should have the await keyword infront of it, in this specific scenario.
There isnt much information regarding this topic yet.
I am trying to simply map() over and array of values, which are used in an Axios get() request, which then returns a then() value, which gets added to the array returned by the map() function.
This is my code right now:
async function get_prices (arrayOfDates, crypto, FIAT) {
// I tried this way (return value can be found below)
let arry_of_prices = arrayOfDates.map(async function(date){
let a = await fetch_data(date, crypto, FIAT)
return a
});
// And this way (return value can be found below)
let arry_of_prices = await arrayOfDates.map(date => fetch_data(date, crypto, FIAT));
}
const fetch_data = (timestamp, crypto, FIAT) => {
axios.get(`https://min-api.cryptocompare.com/data/pricehistorical?fsym=${crypto}&tsyms=${FIAT}&ts=${timestamp}`)
.then(function (response) {
return Object.values(response.data).map(key => Object.values(key))[0][0]
})
.catch(function (error) {
console.log(error);
});
}
let arr = [1468965600, 1469052000, 1469138400,1469484000]
get_prices(arr, "BTC", "USD").then(arr => console.log(arr))
The first try returned 4 resolved promises, but the values were undefined.
Whereas the second return an array with 4 undefined.
Does anyone have an idea of how this could be structured, so that the map() waits until the function is resolved and then adds the value to the array?

map doesn't know anything about promises, so when you use an async callback with map, you'll get an array of promises, not values. If you then want a promise that will resolve when all of those promises have resolved, you can use Promise.all.
However, in your case, you don't need an async function, since you're using fetch_data in the callback. You need to modify fetch_data so it returns the promise from axios.get (and remove the catch handler):
const fetch_data = (timestamp, crypto, FIAT) => {
return axios.get(`https://min-api.cryptocompare.com/data/pricehistorical?fsym=${crypto}&tsyms=${FIAT}&ts=${timestamp}`)
.then(function (response) {
return Object.values(response.data).map(key => Object.values(key))[0][0]
});
}
(This is one of the rules of promises: If you're returning the promise, you don't handle rejections from it [unless you want to convert them into different rejections, or if you can usefully convert them to resolutions instead]. If you don't return the promise, you do handle rejections.)
Now you already have a promise, so you use that in the map (no need for async):
async function get_prices (arrayOfDates, crypto, FIAT) {
let arr_of_price_promises = arrayOfDates.map(function(date){
let a = fetch_data(date, crypto, FIAT)
return a
});
return Promise.all(arr_of_price_promises);
}
Notice I removed the await from in front of fetch_data. We return fetch_data's promise directly, then wait for them all.
Or with an arrow function:
async function get_prices (arrayOfDates, crypto, FIAT) {
let arr_of_price_promises = arrayOfDates.map(date => fetch_data(date, crypto, FIAT));
return Promise.all(arr_of_price_promises);
}

Related

Map array of objects and change one property with a function that calls an API. I keep getting promise pending

I have to loop through an array of objects and modify one single property in each object. I modify this property with a function that connects to the Twitter API. My problem is that I must be using async and await wrongly because I am getting a promise pending.
This is my code:
getProfile:(req,res)=>{
try {
const userId=req.params.id
const profile=db.query('SELECT * FROM profiles WHERE user_id=?',
[userId],async (err,result)=>{
if(err) return res.status(404).send(err)
const profiles= await result.map( obj=>{
const container={}
container['name']=obj.profile_name
container['desc']=obj.profile_desc
container['twitter']= connectTwitt.getTwitt(obj.twitt)//calls to api
return container
})
console.log(profiles)// promise pending
res.send(profiles)
This is the structure of the array of object that I am mapping:
[
{profile_name:`Elon Musk`, profile_desc:'enterpreneur',twitt:636465}
]
Yes, you are using the async/await syntax a little bit incorrectly.
Right now, you are calling await on the Array.map() method. However, that method is not promise-based.
Instead, you have to add the await keyword to the getTwitt() method, and await for all promises to complete.
With those changes, it should look like below.
const profiles = await Promise.all(result.map(async (obj) => { // This line has been modified
const container = {};
container["name"] = obj.profile_name;
container["desc"] = obj.profile_desc;
container["twitter"] = await connectTwitt.getTwitt(obj.twitt); // This line has been modified.
return container;
}));
Hopefully this helps with your <pending> issue!

Async Function returns Promise pending

I don't understand why i get Promise { <pending> } since i used async/await
this is my code
const fetch = require("node-fetch")
function getRandomPokemon() {
var pokemonID = Math.floor(Math.random() * 851);
console.log("The Pokemon Id is " + pokemonID);
return pokemonID
}
async function getJSON() {
let response = await fetch('https://pokeapi.co/api/v2/pokemon/'+ pokemonID);
var json = await response.json
}
var pokemonID = getRandomPokemon()
var json = getJSON()
console.log(json)
All async functions return a promise - always. Using await inside an async function suspends the execution of that function until the promise you are awaiting resolves or rejects, but an async function is not blocking to the outside world. An await does not suspend execution of the whole js interpreter.
At the point of the first await in the async function your function returns a promise back to the caller and the caller continues to execute. That promise is then resolved or rejects when the function eventually completes its work sometime in the future.
I don't understand why i get Promise { } since i used async/await
Because all async functions return a promise.
There are numerous issues in your code:
Your getJSON() function has no return value. That means that the promise it returns resolves to undefined so there's not way to communicate back its value.
await response.json needs to be await response.json().
pokemonID should be passed as an argument, not just stuffed into a higher scoped variable.
When calling getJSON(), you have to use .then() or await on it to get the resolved value from the returned promise.
You may also notice that your getJSON() function has no return value. That means that the promise it returns also resolves to undefined. And, await response.json needs to be await response.json().
Your code needs to be more like this:
async function getJSON(id) {
const response = await fetch('https://pokeapi.co/api/v2/pokemon/'+ id);
const json = await response.json();
return json;
}
const pokemonID = getRandomPokemon();
getJSON(pokemonID).then(data => {
console.log(data);
}).catch(err => {
console.log(err);
});
Three problems:
getJSON() doesn't return anything. It needs to end with return json;.
Declaring a function async makes it return a promise. If you want to get the return value directly rather than a promise, you have to call it with await getJSON(). You can only do that if you're calling it from another async function.
You need to call response.json: var json = response.json();
Just a few changes
Add the return statement to the getJSON() function
Use the .json() function to get the json from the response in the getJSON()
One way to handle a promise is to use .then(). You could also use await but make sure that it's scoped inside an await block.
const fetch = require('node-fetch');
function getRandomPokemon() {
var pokemonID = Math.floor(Math.random() * 851);
console.log('The Pokemon Id is ' + pokemonID);
return pokemonID;
}
async function getJSON() {
let response = await fetch('https://pokeapi.co/api/v2/pokemon/' + pokemonID);
var json = await response.json();
return json
}
var pokemonID = getRandomPokemon();
getJSON().then((json) => {
console.log(json);
});
response.json should be a function.
On your getJSON function definition, you are doing await response.json.
Change it to await response.json().
Notice the parenthesis after json().
Since the getJSON function returns promise, you have to await the response
On your second last line, add var json = await getJSON();
furthermore, in order to use await, you must wrap your code in an async function
Wrap your last and second-last line in an async function
async function resolveJSON(){
var json = await getJSON(); // waits for the promise to fulfill
console.log(json);
}

external function call, promise, async and Mongo - confused

Beginning to feel really thick here. Read a lot and I believe I understand promises and async-await decently well. However, I seem to struggle to use the function elsewhere, such that I can obtain the result (e.g. i get pending in another js file with: let dbConnection = dbOperations.openDatabaseConnection();).
Could someone explain to me why do I keep getting pending from the below functions (same function written with promise and asyncawait)? I can console.log the dbConnection result as expected prior to my return within the function. Also, I am particularly keen to understand promises in this sense, as it seems that many npm packages seem to return promises (and with my experience at least the async-await does not sit well with that? -> using async does not wait for resolve in my experience).
// Establish database connection
function openDatabaseConnection() {
let dbConnection = {};
return mongodb.connect(dbUri).then(conn => {
dbConnection.connection = conn;
return dbConnection;
})
.then(() => {
dbConnection.session = dbConnection.connection.db(dbName);
//console.log(dbConnection);
return dbConnection;
})
.catch(err => {
throw err;
});
};
// Establish database connection
async function openDatabaseConnection() {
let dbConnection = {};
try {
dbConnection.connection = await mongodb.connect(dbUri);
dbConnection.session = await dbConnection.connection.db(dbName);
} finally {
//console.log(dbConnection);
return dbConnection;
};
};
Both functions return again a promise.
So in your statement let dbConnection = dbOperations.openDatabaseConnection();
you assign a promise.
Thus you need to do something like:
dbOperations.openDatabaseConnection().then((dbConn) => ..)
or
let dbConnection = await dbOperations.openDatabaseConnection();
(note this requires to be wrapped in an async function)
Async/await is just another way to work with Promises, just don't wait for something that isn't a Promise.
async function openDatabaseConnection() {
let dbConnection = {};
try {
dbConnection.connection = await mongodb.connect(dbUri);
// await here does not make sense, this function does not return a Promise
// dbConnection.session = await dbConnection.connection.db(dbName);
dbConnection.session = dbConnection.connection.db(dbName);
} finally {
//console.log(dbConnection);
// return will always execute, keep here only when it should
// return an empty object if the connection fails
return dbConnection;
};
};
More info on async/await

await with array foreach containing async await [duplicate]

This question already has answers here:
Using async/await with a forEach loop
(33 answers)
Closed last year.
In node.js I need to use a function procesMultipleCandidates () which contains Array.foreach which process insert every element into db. but the entire function should return response after completing all insertion operation
JavaScript Code
async function procesMultipleCandidates (data) {
let generatedResponse = []
await data.forEach(async (elem) => {
try {
// here candidate data is inserted into
let insertResponse = await insertionInCandidate(elem)
//and response need to be added into final response array
generatedResponse.push(insertResponse)
} catch (error) {
console.log('error'+ error);
}
})
console.log('complete all') // gets loged first
return generatedResponse // return without waiting for process of
}
And as described above last return statement not waiting for the foreach execution to complete first.
Use Array.prototype.map and Promise.all:
async function procesMultipleCandidates (data) {
let generatedResponse = []
await Promise.all(data.map(async (elem) => {
try {
// here candidate data is inserted into
let insertResponse = await insertionInCandidate(elem)
// and response need to be added into final response array
generatedResponse.push(insertResponse)
} catch (error) {
console.log('error'+ error);
}
}))
console.log('complete all') // gets loged first
return generatedResponse // return without waiting for process of
}
Or use a for/of loop if you don't want the loop run concurrently:
async function procesMultipleCandidates (data) {
let generatedResponse = []
for(let elem of data) {
try {
// here candidate data is inserted into
let insertResponse = await insertionInCandidate(elem)
// and response need to be added into final response array
generatedResponse.push(insertResponse)
} catch (error) {
console.log('error'+ error);
}
}
console.log('complete all') // gets loged first
return generatedResponse // return without waiting for process of
}
Array.prototype.forEach() tries to execute sequentially and you may not always get the expected result.
Array.prototype.map() is the widely used for creating Promises using data and then resolved using await Promise.all([])
using .map() function, all the promises are resolved in parallel and if you happen to call an API you may end up getting error 429: Too many Requests
To execute all the promises sequentially, you can use for ...of.
const array1 = ['a', 'b', 'c'];
for (const element of array1) {
console.log(element);
}
MDN Reference
As far as I'm concern, for of works better with asynchrony than forEach. Anyway, in this case, if you only care for the aggregated result, I think this could be a solution.
async function procesMultipleCandidates(data) {
const promises = data.map(insertionInCandidate);
return await Promise.all(promises);
}
Use Promise.all instead, which will resolve once all Promises in the array passed to it have resolved. The syntax will probably be simplified if you use and return a plain Promise chain rather than async/await:
const procesMultipleCandidates = data => Promise.all(
data.map(insertionInCandidate)
)
.catch(err => {
console.log('err: ' + err);
});
Note that if there's an error, procesMultipleCandidates will currently return a resolved Promise rather than rejecting - it might be a better idea to have the consumer of procesMultipleCandidates handle errors, so that it can handle them appropriately (rather than simply console.logging it).

React Native AsyncStorage returns promise instead of value

I understand this is a very common problem in RN and I am still trying to understand the very possible advantage of returning a promise when loading data from a property file instead of just returning the value, which makes chaining requests very cumbersome...but anyway. Here is what I have right now, which is a wrapper from the AsyncStorage RN implementation:
multiGet = async (key) => {
var value = null;
try {
value = await AsyncStorage.multiGet(key).then(
(values) => {
value = values;
console.log('Then: ',values);
});
} catch (error) {
console.log('Error: ',error);
}
console.log('Final: ',value);
return value;
}
At this point, value gets undefined. In my main code I have this:
var filter = this._getFilter();
console.log('returned filter:',filter);
The _getFilter function is the one using the AsyncStorage wrapper but the 'returned filter' is logging before the first function so it is not waiting for the returned values before continue, so I get an undefined value.
At first, I thought that just by using the async/await the AsyncStorage wold return a value instead of a promise but after testing, the value I get from:
value = await AsyncStorage.getItem('key')
is STILL a promise, so I have to use then() to read the value.
Basically the order that I am seeing in the logs is:
_getFilter
returned value: undefined
Then: value: here I get the correct value from the keys but the code already passed and I don't have the correct value in the variable
I have no clue what is going on or how to handle this correctly. This is supposed to be very simple and common use case.
I would love to solve this without using a third party module.
Thanks
SOLUTION
Edit: After understanding a little more about the concepts of async/await and callbacks, I finally have a code that works. I don't like it, because it makes the code very hard to read. I might need to refactor it to use promises but for now, it works. Here are some snippets in case someone finds the same issue:
this._getFilter(body,this._filterSearchCallback,callback);
Note: I am sending the body through the chain because I am "completing" the information as I pass the functions. The second parameter is the first callback that actually makes a fetch query and the third callback is the return of the fetch function.
_getFilter(body,callback,returnCallback){
{...}
this._sh.multiGet(keysBanks).then(
(banks) => {
filter.banks = banks;
console.log(banks);
this._sh.multiGet(keysCards).then(
(cards) => {
console.log(cards);
filter.credit_cards = cards;
callback(body,filter,returnCallback);
});
}
);
}
Here basically I am chaining a couple of gets because I need several values from the store. This is the part I dont really like. _sh is my StorageHelper which is a wrapper to the AsyncStorage, nothing fancy.
multiGet = async (key) => {
const value = await AsyncStorage.multiGet(key);
return value;
}
Then my very last callback that actually makes the fetch and send the JSON response to the main screen in react native:
_filterSearchCallback(body,filter,callback){
body.filter = filter;
return fetch(apiUrl, {method: 'post', body: JSON.stringify(body)})
.then((response) => response.json())
.then((responseJson) => {
callback(responseJson);
})
.catch((error) => {
console.error(error);
callback(responseJson);
});
}
I will improve this and make it cleaner but for now, it works. Hope it helps others too.
Once upon a time, i was having the same problem so what I did I will share with you here.
Basically, your execution is moving forward without taking any value i.e undefined what you are getting right now so there are 3-4 ways to get out of this:
1) async await
2) callback
1) We will start with the callback which is use by most of the people.
We will use your code to implement this:
_getFilter(key,callback)
{
multiGet = (key) => {
var collect;
try {
var value = AsyncStorage.multiGet(key).then(
(values) => {
// value = values;
console.log('Then: ',values);
callback(values)
});
} catch (error) {
console.log('Error: ',error);
}
console.log('Final: ',value);
}
}
this._getFilter(key,function(filter){
console.log('returned filter:',filter);
});
2)async/await
If you are using await alone then you would get an error, to use await inside a function you have to declare the async function by setting async keyword before the function name.
async _getFilter(key)
{
multiGet = async (key) => {
var value,collect;
try {
value = await AsyncStorage.multiGet(key).then(
(values) => {
collect= values;
console.log('Then: ',values);
});
} catch (error) {
console.log('Error: ',error);
}
console.log('Final: ',value);
return collect;
}
//calling the async function
this._getFilter(key).then((filter)=>{
if(filter!=null)
console.log('returned filter:',filter)
else
console.log('error')
})
Hope this would clear your concepts and help you with other react native developers.I have seen lots of people struggling with this thing so today I got the chance to clear your doubts.
Cheers :)
the thing is await turns the promise into a value, you don't need to use .then(). Try the following:
const keys = await AsyncStorage.getAllKeys()
const values = await AsyncStorage.multiGet(keys)
// at this point `values` will have data for all keys

Categories

Resources