This question already has answers here:
Why do I need to await an async function when it is not supposedly returning a Promise?
(3 answers)
Closed 11 months ago.
I am trying to get an auth token from an API and then store that Auth token and use it in subsequent calls to the API.
This is the code to get the token
const getToken = async (): Promise<string | void> => {
const response = await fetch('https://exampleapi.com/auth', {
method: 'POST',
body: JSON.stringify(body),
headers: headers,
agent: httpsAgent
})
const data = await response.text().then(txt => {
const cred = xml2json(txt);
const cred2: authResponse = JSON.parse(cred);
const tkn = cred2.elements[0].elements[0].attributes.token;
return tkn;
}).catch(err => console.log(err));
return data;
}
However it is returning a promise even though I am .then() and .catch() -ing.
If I put a console.log() within the .then(), I am able to see the token that I want. However I'm not able to return it as a value so that it can be stored.
const tkn = getToken().then(data => console.log(data)).catch(err => console.log(err));
I want the value of the token to be returned, not just to be able to console.log() the value I'm looking for. All the examples I am seeing are simply showing that I can console.log() the value I am looking for within the .then(), however that is not what I am trying to do. Sorry if this is not worded correctly would be happy to update with any relevant information needed. Would like to avoid top level await if possible.
You seem to be mixing await and .then() / .catch(). Change the second async request to:
try {
const text = await response.text();
const cred = xml2json(txt);
const cred2: authResponse = JSON.parse(cred);
const data = cred2.elements[0].elements[0].attributes.token;
return data;
} catch(error) {
console.log(err);
}
Related
I have an API called getQuote and a component called QuoteCard. Inside QuoteCard I'm trying to render an array of users that liked a quote. The API works fine, I have tested it, and the code below for getting the users works fine too.
const Post = async (url, body) => {
let res = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
"accept": "*/*"
},
body: JSON.stringify(body)
}).then(r => r.json());
return res;
}
const getAllLikes = async () => {
let users = await Post('api/getQuote', {
id: "639e3aff914d4c4f65418a1b"
})
return users
}
console.log(getAllLikes())
The result is working as expected :
However, when trying to map this promise result array to render it onto the page is where I have problems. I try to render like this:
<div>
{getAllLikes().map((user) => (
<p>{user}</p>
))}
</div>
However, I get an error that states:
getAllLikes(...).map is not a function
I don't understand why this is happening. Why can't I map the array? Is it because it's a promise or something?
And if anyone needs to see the getQuote API, here it is:
//Look ma I wrote an API by myself! :D
import clientPromise from "../../lib/mongodb";
const ObjectId = require('mongodb').ObjectId;
import nc from "next-connect";
const app = nc()
app.post(async function getQuote(req, res) {
const client = await clientPromise;
const db = client.db("the-quotes-place");
try {
let quote = await db.collection('quotes').findOne({
_id: new ObjectId(req.body.id)
})
res.status(200).json(JSON.parse(JSON.stringify(quote.likes.by)));
} catch (e) {
res.status(500).json({
message: "Error getting quote",
success: false
})
console.error(e);
}
})
export default app
Thanks for any help!
It is due to the fact that getAllLikes is an async function and thus it returns promise which does not have a map function.
You can either save it in a state variable before using await Or chain it with .then.
Minimal reproducible example which works
const getAllLikes = async () => {
return ['a', 'b']
}
getAllLikes().then((r) => r.map((g) => { console.log(g) }))
Edit: The above code won't work if directly used with jsx since the return of getAllLikes will still be a promise. Solution would be to save it in a state variable and then using it.
I am from Angular and I believe we call pipe on Observables (or Promises). Map can then be called inside the pipe function
observable$ = getAllLikes().pipe(map( user => <p>{user}</p>))
If there is no pipe, I can only think of manually subscribing (which is not a good practice)
sub$ = getAllLikes().subscribe( user => <p>{user}</p>)
// unsub from sub$ appropriately
// We do this from ngOnDestroy in angular
ngOnDestroy() {
this.sub$?.unsubscribe()
}
The 1st example logs the resolved value of the promise from the fetch.
The 2nd example logs the pending promise object to the console which I then have to .then((res) => {console.log(res)}) to get the resolved value.
I'm using async functions so I thought both examples were equivalent...?
I have not shown my API key but it works when I use it in the code.
1st Example:
const apiKey = 'somekey';
const city = 'Berlin'
const getWeather = async (cityArg, apiKeyArg) => {
let city = cityArg;
let apiKey = apiKeyArg;
try{
const response = await fetch(`https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=${apiKey}`);
if(response.ok) {
let jsonResponse = await response.json();
console.log(jsonResponse);
//return jsonResponse;
}
} catch(error) {
console.log(error);
}
}
getWeather(city, apiKey);
//console.log(getWeather(city, apiKey));
2nd Example:
const apiKey = 'somekey';
const city = 'Berlin'
const getWeather = async (cityArg, apiKeyArg) => {
let city = cityArg;
let apiKey = apiKeyArg;
try{
const response = await fetch(`https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=${apiKey}`);
if(response.ok) {
let jsonResponse = await response.json();
//console.log(jsonResponse);
return jsonResponse;
}
} catch(error) {
console.log(error);
}
}
//getWeather(city, apiKey);
console.log(getWeather(city, apiKey));
The reason is that you are awaiting inside that async function, but not in the console.log at the bottom.
Anything outside of that async is going to continue on running as normal. So the console.log(getWeather(city, apiKey) keeps running even though that function has not gotten a response yet. There are a few solutions. First, you could await getWeather, which requires wrapping it in a function.
await function secondFunc(){
console.log(await getWeather(city, apiKey));
}
secondFunc();
In my opinion, the better way is to use .then. I almost always use it, it is cleaner and more logical to me. Don't forget that you can chain promise statements.
fetch(`https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=${apiKey}`)
.then(response => response.json())
.then((response)=>{
console.log(response);
//Other code to do stuff with the response
})
.catch((error)=>{
console.log(error);
});
Another way to think of it is that the getWeather is Async, which will wait for a response, and return a promise in the meanwhile. But console.log is not async. So console.log keeps running as usual, but it has only recieved a Promise from getWeather, because that function is not resolved.
Hope that is clear. If you don't quite understand, let me know and I will do my best to explain further.
async functions return promises and since console.log isn't an async function it won't wait for getWeather() to resolve and will just log pending
hope that clears it up
id recommend just using .then()
//you could do
getWeather(city,ApiKey).then((response) =>{console.log(response));
hope that was helpful
This question already has answers here:
How do I return the response from an asynchronous call?
(41 answers)
Closed 2 years ago.
How do I ensure a promise has resolved in my configuration file. For example my configuration looks like the below.
const fetch = require("node-fetch");
const fetchToken = async () => {
return await fetch('www.token-endpoint.com', {
method: "POST",
headers: {
ContentType: "application/x-www-form-urlencoded",
},
body: new URLSearchParams({
secret: "this is a secret",
})
})
}
const ACCESS_TOKEN = fetchToken()
.then((res) => { return res.json() })
.then(...)
// And so on
// Now I want my promise to definitely resolve
module.exports = {
accessToken: ACCESS_TOKEN // This will be undefined unless my promise has resolved
}
After this I would like to use the token.
const config = require("./config")
// Use config.accessToken knowing it is well defined.
you can do something like this ,node fetch actually has
a built in function for converting the response to JSON, but it does not do it automatically in the same way that Axios and SuperAgent do. recent versions of this library use promises, so we're able to use async/await syntax with it as well:
const fetch = require('node-fetch');
(async () => {
try {
const response = await fetch('www.token-endpoint.com')
const json = await response.json()
console.log(json.url);
console.log(json.explanation);
} catch (error) {
console.log(error.response.body);
}
})();
This question already has answers here:
Why is my variable unaltered after I modify it inside of a function? - Asynchronous code reference
(7 answers)
Closed 2 years ago.
I am creating an array of objects from an endpoint with the fetch api. Each object in the response contains a new url and I scan this array for new objects. The purpose is to access the endpoint, obtain the urls and access these urls to store your objects and attributes. But when accessing the array with objects and from an index it returns undefined.
let url = [];
let pokemon = [];
function getPokemons(){
fetch("https://pokeapi.co/api/v2/type/11/")
.then(async response =>{
await response.json().then(data =>
data.pokemon.forEach(item =>
url.push(item.pokemon.url)
)
)
}).then( response => {
url.map(item =>
fetch(item).then(response =>{
response.json().then(data =>
pokemon.push(data)
)
})
)
})
}
getPokemons()
console.log(pokemon[1])
Solution for web browser :
The node-fetch module is not required, I just clean your code using async/await syntax.
// getPokemon is now an async function for cleaner syntax
async function getPokemon() {
const pokemon = [];
// Then we can use await
const response = await fetch("https://pokeapi.co/api/v2/type/11/"),
json = await response.json(),
urls = json.pokemon.map(item => item.pokemon.url);
for (const url of urls) {
const response = await fetch(url),
json = await response.json();
pokemon.push(json)
}
// We return the pokemon array instead of polluting global scope
return pokemon;
};
getPokemon().then(pokemon => {
console.log(pokemon)
});
Hope it help !
NodeJS solution
You must install node-fetch (see npmjs.org :
npm install -D node-fetch
Then you can use a fetch:
// Importing the node module.
// You can delete this line if your not using a node environment.
const fetch = require('node-fetch');
// getPokemon is now an async function for cleaner syntax
async function getPokemon() {
const pokemon = [];
// Then we can use await
const response = await fetch("https://pokeapi.co/api/v2/type/11/"),
json = await response.json(),
urls = json.pokemon.map(item => item.pokemon.url);
for (const url of urls) {
const response = await fetch(url),
json = await response.json();
pokemon.push(json)
}
// We return the pokemon array instead of polluting global scope
return pokemon;
};
getPokemon().then(pokemon => {
console.log(pokemon)
});
Explanation
fetch is not part of the Javascript language specification but is a Web API. Each browser may or not choose to implement it. Each Implementations may work differently under the hood but the Javascript API provided must match the standard (MDN Web Docs).
This is why you need a module to fetch the data.
EDIT : Adding solution for web browser environment
The problem is that the function getPokemons should be async.
You should await it before accessing:
getPokemons().then(()=> console.log(pokemon[1]))
// or if it inside an async function:
await getPokemons();
console.log(pokemon[1]);
But there's another reason also. You have internal promises outside the parent promise chain. I mean:
.then((response) => {
// this is array of promises
// you should return it to have ability await it on parent promise
url.map((item) =>
fetch(item).then((response) => {
response.json().then((data) => pokemon.push(data));
})
);
});
Your code might look like:
// if you really need global variable
let pokemon = [];
async function getPokemons() {
const result = await fetch("https://pokeapi.co/api/v2/type/11/")
.then((response) => response.json())
.then((data) =>
Promise.all(
data.pokemon.map((item) =>
fetch(item.pokemon.url).then((response) => response.json())
)
)
);
pokemon.push(...result);
}
getPokemons().then(() => {
console.log(pokemon[1]);
});
Or, the same result without global variables:
function getPokemons() {
return fetch("https://pokeapi.co/api/v2/type/11/")
.then(...)
.then(...);
}
getPokemons().then((pokemons) => {
console.log(pokemons[1]);
});
My 2 API calls happen to be at the same time, where the response of API1 is to be sent as a request parameter to API2. But, the value goes as undefined because it isn't fetched till that time. Is there any way this can be solved in react.
There are multiple ways to solve this problem, I will explain one of the latest as well most sought after ways of solving the problem.
I am sure you would have heard of async/await in JavaScript, if you haven't I would suggest you to go through an MDN document around the topic.
There are 2 keywords here, async && await, let's see each of them one by one.
Async
Adding async before any function means one simple thing, instead of returning normal values, now the function will return a Promise
For example,
async function fetchData() {
return ('some data from fetch call')
}
If you run the above function in your console simply by fetchData(). You'd see that instead of returning the string value, this function interestingly returns a Promise.
So in a nutshell async ensures that the function returns a promise, and wraps non-promises in it.
Await
I am sure by now, you would have guessed why we use keyword await in addition to async, simply because the keyword await makes JavaScript wait until that promise (returned by the async function) settles and returns its result.
Now coming on to how could you use this to solve your issue, follow the below code snippet.
async function getUserData(){
//make first request
let response = await fetch('/api/user.json');
let user = await response.json();
//using data from first request make second request/call
let gitResponse = await fetch(`https://api.github.com/users/${user.name}`)
let githubUser = await gitResponse.json()
// show the avatar
let img = document.createElement('img');
img.src = githubUser.avatar_url;
img.className = "promise-avatar-example";
document.body.append(img);
// wait 3 seconds
await new Promise((resolve, reject) => setTimeout(resolve, 3000));
img.remove();
return githubUser;
}
As you can see the above code is quite easy to read and understand. Also refer to THIS document for more information on async/await keyword in JavaScript.
Asyn/await solves your problem:
const requests = async () =>{
const response1 = await fetch("api1")
const result1 = await response1.json()
// Now you have result from api1 you might use it for body of api2 for exmaple
const response2 = await fetch("api2", {method: "POST", body: result1})
const result2 = await response1.json()
}
If you're using react hooks, you can use a Promise to chain your API calls in useEffect
useEffect(() => {
fetchAPI1().then(fetchAPI2)
}, [])
relevant Dan Abramov
fetch(api1_url).then(response => {
fetch(api2_url, {
method: "POST",
body: response
})
.then(response2 => {
console.log(response2)
})
})
})
.catch(function (error) {
console.log(error)
});
or if using axios
axios.post(api1_url, {
paramName: 'paramValue'
})
.then(response1 => {
axios.post(api12_url, {
paramName: response1.value
})
.then(response2 => {
console.log(response2)
})
})
.catch(function (error) {
console.log(error);
});