I have a JS program that does a whole lot of fetch() calls to a specific API. I want to abstract all the fetch() calls into a single class called "apiService" so my code will be more readable. I want the apiService to apply some intelligence and then return responses to the caller, in the following ways:
- apiService should check the response to see if there are errors present, which it must always process in the same way.
- fetch() will sometimes receive a "res" that is raw data and should be used as is, and sometimes it'll received json that needs a .then(res => res.json().then(res applied so it can return an object.
So I can't just do a "return fetch(..." from apiService, because apiService needs to process one or more .then() blocks with the response. But I also need to return something that causes the calling code to work asynchronously and not block and wait.
Anyone know how I could structure the apiService function to process the html responses but also return asynchronously i.e. the calling function would receive the result object after the error checking etc.
So I can't just do a "return fetch(..." from apiService, because apiService needs to process one or more .then() blocks with the response. But I also need to return something that causes the calling code to work asynchronously and not block and wait.
This gives me the feeling that you might be misunderstanding promises a little bit. Take this example:
const doAsyncWork = () => fetch('somewhere').then(() => console.log('fetch is complete'))
// use the above function
doAsyncWork().then(() => console.log('used the fetching function'))
The output of the above code will be
fetch is complete
used the fetching function
As you can see, by chaining then after the fetch call, you are actually returning the result of then, and not fetch. Another way to think of it is, what are you actually returning if you called
const result = a().b().c() // we are really returning the result of `c()` here.
With the above in mind you can most definitely do something like:
const apiCall = loc => fetch(loc).then(res => {
// do things with your response
return res
})
apiCall('someEndpoint').then(finalRes => {
console.log('this is called after fetch completed and response processed')
})
There is a nice article about it here called "Synchronous" fetch with async/await that will break it down for you in pieces.
In Short:
You can use await when using fetch():
const response = await fetch('https://api.com/values/1');
const json = await response.json();
console.log(json);
First we wait for the request to finish, Then we can wait for it to complete (or fail) and then pass the result to the json variable.
The complete example would be to use async, because `await won't work without it:
const request = async () => {
const response = await fetch('https://api.com/values/1');
const json = await response.json();
console.log(json);
}
request();
I thinks you can fulfill your requirement using Promise.all()
Here is an example for you.
var promise1 = Promise.resolve(3);
var promise2 = 42;
var promise3 = new Promise(function(resolve, reject) {
setTimeout(resolve, 100, 'foo');
});
Promise.all([promise1, promise2, promise3]).then(function(values) {
console.log(values);
});
For more info you can refer:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all
You can use a library called axios instead of worrying about promises and data formats yourself.
However, if you still want to do it, use the following way.
you can use a method to create promises like this.
makeRequest(url, requestData) {
const response = await fetch(url, requestData)
.then(response => { console.info('network request successful to', url); return response.json() })
.then(json => {
console.info('response received for request', url, requestData, json)
return json;
})
.catch(e => {
console.error('error at request', url, requestData, e);
return e
});
return response;
}
and use the promises like this
makeRequest('someurl', {
method: 'GET'
}).then(response=>{/*Your logic*/}).catch(error=>{/*Your logic*/});
Related
my fetch is stuck in pending when I query a fastapi endpoint in local dev.
followed this blog and a few others - https://damaris-goebel.medium.com/promise-pending-60482132574d
Using this fetch code (having simplified it drastically just to get a simple solution working)
function fastapiRequest(path) {
return fetch(`${path}`)
.then((response) => {
return response;
}
);
into a constant variable i.e.,
const xxxx = fastapiRequest(
`http://0.0.0.0:8008/xcascasc/Dexaa/Emo.json?Magic=Dexxaa&emotion=expressions`
);
Ideally I want to use UseSWR to do this as I'm using next.js, but first of all, just need it to work :)
A postman query like this works fine to return a value
curl --location --request GET 'http://0.0.0.0:8008/xcaxc/dexxa/emo.json?analysis=statistical&substance=dexxa&emo=powa' \
--header 'x_token: 13wdxaxacxasdc1'
the value is left like this in console.log
data show here? Promise {<pending>}
With the initial response being
Response {type: 'cors', url: 'url', redirected: false, status: 200, ok: true, …}
Update based on answers.
Using each of the proposed answers, I am still not getting the data returned appropriately. i.e.,
function fastApiRequest(path) {
console.log("really begins here");
return fetch(`${path}`, { mode: 'cors' })
.then((response) => {
console.log('response', response);
return response.json();
})
.catch((err) => {
throw err;
});
}
async function test() {
console.log('begins');
return await fastApiRequest(
`http://0.0.0.0:8008/xxxx/dex/adea.json?deaed=adedea&adee=deaed&adeada=adeeda`
);
}
const ansa = test();
Is giving a response of pending at the moment.
The backend is built with fastapi, with these CORS, I'm wondering if I need to give it more time to get the data? (postman works fine :/)
def get_db():
try:
db = SessionLocal()
yield db
finally:
db.close()
origins = [
"http://moodmap.app",
"http://localhost:3000/dashboard/MoodMap",
"http://localhost:3000",
"http://localhost",
"http://localhost:8080",
]
app.add_middleware(
CORSMiddleware,
allow_origins=origins,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
max_age=3600,
)
I am running the fastapi code in a docker container as well btw
As per Documentation
The Response object, in turn, does not directly contain the actual JSON response body but is instead a representation of the entire HTTP response. So, to extract the JSON body content from the Response object, we use the json() method, which returns a second promise that resolves with the result of parsing the response body text as JSON.
.json() is an async method (it returns a Promise itself), so you have to assign the parsed value in the next .then(). So your code can be changed like this.
function fastApiRequest(path) {
let res;
fetch(`${path}`)
.then((response) => response.json())
.then((data) => (res = data))
.then(() => console.log(res));
return res;
}
response = fastApiRequest('https://proton.api.atomicassets.io/atomicassets/v1/accounts?limit=10');
console.log('response')
If you want to use async/await approach, below is the code.
async function fastApiRequest(path) {
try {
const response = await fetch(path);
const data = await response.json();
return data;
} catch (error) {
console.error(error);
}
}
async function test() {
console.log(await fastApiRequest('https://proton.api.atomicassets.io/atomicassets/v1/accounts?limit=10'))
}
test()
first you need to parse the response into json if it's a json API.
function fastapiRequest(path) {
return fetch(`${path}`)
.then((response) => {
return response.json();
});
}
you need to 'await' for the rsponse
you need to write the below code in an async function
const xxxx = await fastapiRequest(
`http://0.0.0.0:8008/xcascasc/Dexaa/Emo.json?Magic=Dexxaa&emotion=expressions`
);
When you make an http request using fetch in javascript it will return a Promise, it's not stuck it's just need to be resloved, you can resolve it just like the above code with async await, or you can use the .then(() => { /* code... */ }) function, you can also use .catch(() => { /* handle error... */ }) function to handle errors.
In Your curl you use x_token as header variable, if it's required you need to pass a header with your path too. All other answers are valid too.
Trying to make an API request with error checking and then be able to use the data from the API outside the async function. This is returning a pending promise right now. Not sure how get useable data out of the API that I can then further use throughout the program. Suggestions?
const fetch = require('node-fetch')
const url = 'https://employeedetails.free.beeceptor.com/my/api/path'
async function getData(url){
try {
const response = await fetch(url)
if (!response.ok) {
throw new Error ("why did I become a software engineer?");
}
else {
return await response.json();
}
}
catch (error) {
console.log(error);
}
}
const data = getData(url);
console.log(data);
async functions return a promise. To get the usable data you will have to either await getData(url) (create another async function for this) or in your example you can write:
getData(url).then(data => console.log(data);
I think the problem here is that the async function is returning a promise so you should add another asyn function to get that data, or maybe you should add a global variable before the function and then change its value
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);
});
This question already has answers here:
How do I return the response from an asynchronous call?
(41 answers)
Closed 3 years ago.
When attempting to use asynchronous calls, the response is undefined rather than returning back the expected response data.
You can see how to run into this issue with any of the following examples:
Fetch
const response = getDataWithFetch();
console.log(response);
function getDataWithFetch() {
const url = 'https://jsonplaceholder.typicode.com/todos/1';
fetch(url).then(response => {
return response.json();
});
}
Axios
const response = getDataWithAxios();
console.log(response);
function getDataWithAxios() {
const url = 'https://jsonplaceholder.typicode.com/todos/1';
axios.get(url).then(response => {
return response.data;
});
}
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
AJAX / jQuery $.get
const response = getDataWithAjax();
console.log(response);
function getDataWithAjax() {
const url = 'https://jsonplaceholder.typicode.com/todos/1';
$.get(url, (response) => {
return response;
});
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
Promise
const response = getDataWithPromise();
console.log(response);
function getDataWithPromise() {
new Promise(resolve => {
const response = 'I am a response';
return resolve(response);
});
}
Overview
Asynchronous calls, such as HTTP get requests via fetch, axios, AJAX or other asynchronous calls via Promises all behave similarly in that what is returned by the time that the function reaches its last line is not actually the result that you want.
Explanation of Issue
This is because asynchronous calls do not wait for the call to finish executing before executing the next line:
Note that in all the examples below, I will be using fetch but the concept could easily be extended to the other call types.
getDataWithFetch();
function getDataWithFetch() {
console.log('start of outer function');
const url = 'https://jsonplaceholder.typicode.com/todos/1';
fetch(url).then(response => {
console.log('start of async function');
response.json();
console.log('end of async function');
});
console.log('end of outer function');
}
As you can see via Run code snippet, the outer function starts and ends before the async function starts. What is undefined being returned from getDataWithFetch() because it is hitting the end of the function, and has no return statement. If we change the code to add a return statement, we can see what part of the function call is actually being returned:
const response = getDataWithFetch();
console.log(response);
function getDataWithFetch() {
const url = 'https://jsonplaceholder.typicode.com/todos/1';
fetch(url).then(response => {
return response.json();
});
return 'hello world';
}
Now the function isn't returning undefined anymore, but it's not returning the fetch request response; instead, it is returning 'hello world'. This is because the fetch request is not blocking the rest of the getDataWithFetch function from executing. To understand why, we need to understand what fetch is doing behind the scenes.
Promises
Resolve
To do so, we need to understand Javascript Promises. There's a great beginner-friendly read on it here: https://javascript.info/promise-basics, but in a nutshell:
A Promise is an object that, when it finishes the asynchronous work, it will call the "resolve" function. Where "resolve" returns the expected data via resolve(dataToReturn).
So behind the scenes, fetch returns a Promise that will call resolve when the fetched data is retrieved, like so:
// just an example of what fetch somewhat looks like behind the scenes
function fetch(url) {
return new Promise(resolve => {
// waiting for response to come back from server
// maybe doing a bit of additional work
resolve(responseDataFromServer);
});
}
With that understanding, we now know that we need to return the call to fetch to actually return the Promise, like so:
const response = getDataWithFetch();
console.log(response);
function getDataWithFetch() {
const url = 'https://jsonplaceholder.typicode.com/todos/1';
return fetch(url).then(response => response.json());
}
The console.log is no longer returning undefined, but it's not returning the right response either. That's because getDataWithFetch is now returning the Promise object from fetch. It's time to get the response from resolve.
.then
To get the data passed to the resolve function, we need to use the then method on the returned Promise.
A Promise provides the method then to retrieve the value passed to resolve as such: myReturnedPromise.then(dataToReturn => { /* do something with the data */ });. then will only execute once the dataToReturn is ready to be handled.
Final Solution
So by using the then method, we can get the actual response printed to console:
getDataWithFetch().then(response => {
console.log(response);
});
function getDataWithFetch() {
const url = 'https://jsonplaceholder.typicode.com/todos/1';
return fetch(url).then(response => response.json());
}
And there you have it!
Overly Verbose Solution
If there was too much magic in what made that solution work, you can also see the breakdown of it by explicitly returning a Promise to be fulfilled:
getDataWithFetch().then(response => {
console.log(response);
});
function getDataWithFetch() {
return new Promise(resolve => {
const url = 'https://jsonplaceholder.typicode.com/todos/1';
return fetch(url).then(response => {
resolve(response.json()));
// Note that the `resolve` does not have to be part of the `return`
}
});
}
Async and Await Solution
Recent improvements to Javascript have provided us with a more succinct way of handling Promises to make them appear synchronous instead of asynchronous. Note that the following code is equivalent to the above solutions, but much easier to read:
async function getDataWithFetch() {
const url = 'https://jsonplaceholder.typicode.com/todos/1';
const response = await fetch(url);
return response.json();
}
getDataWithFetch().then(response => {
console.log(response);
});
async and await are clean and powerful alternatives to explicitly dealing with Promises, especially when Promise Chaining is involved. I recommend reading more about them here: https://javascript.info/async-await.
I'm quite a newbie in JavaScript and in Promises.
I'm trying to build an array of objects that I get from an API.
To do so, I've build two functions in a file MyFile.js.
The first one returns a promise when an axios promise is resolved. It's
function get_items (url) {
return new Promise((resolve, reject) => {
let options = {
baseURL: url,
method: 'get'
}
axios(options)
.then(response => {
resolve(response.data)
})
.catch(error => {
reject(error.stack)
})
})
}
The second one looks like this:
let output = []
let next_url = 'https://some_url.com/api/data'
async function get_data () {
try {
let promise = new Promise((resolve, reject) => {
if (next_url) {
get_items(next_url)
.then(response => {
output.push(...response.results)
if (response.next) {
next_url = response.next
console.log('NEXT_URL HERE', next_url)
get_data()
} else {
console.log('else')
next_url = false
get_data()
}
})
.catch(error => {
reject(error.stack)
})
} else {
console.log('before resolve')
resolve(output)
}
})
return await promise
} catch(e) {
console.log(e)
}
}
It's where I'm grinding my teeth.
What I think I understand of this function, is that:
it's returning the value of a promise (that's what I understand return await promise is doing)
it's a recursive function. So, if there is a next_url, the function continues on. But if there is not, it gets called one last time to go into the else part where it resolves the array output which contains the results (values not state) of all the promises. At least, when I execute it, and check for my sanity checks with the console.log I wrote, it works.
So, output is filled with data and that's great.
But, when I call this function from another file MyOtherFile.js, like this:
final_output = []
MyFile.get_data()
.then(result => {
console.log('getting data')
final_output.push(...result)
})
it never gets into the then part. And when I console.log MyFile.get_data(), it's a pending promise.
So, what I would like to do, is be able to make get_data() wait for all the promises result (without using Promise.all(), to have calls in serie, not in parallel, that would be great for performances, I guess?) and then be able to retrieve that response in the then part when calling this function from anywhere else.
Keep in mind that I'm really a newbie in promises and JavaScript in general (I'm more of a Python guy).
Let me know if my question isn't clear enough.
I've been scratching my head for two days now and it feels like I'm running in circle.
Thanks for being an awesome community!
This is a bit untested
const api_url = 'https://some_url.com/api/data';
get_data(api_url).then((results) => {
console.log(results);
}).catch((error) => {
// console.error(error);
});
function get_items (url) {
const options = {
baseURL: url,
method: 'get'
};
return axios(options).then((response) => response.data);
}
async function get_data(next_url) {
const output = [];
while (next_url) {
const { results, next } = await get_items(next_url);
output.push(...results);
next_url = next;
}
return output;
}
Basically it makes things a bit neater. I suggest to look at more examples with Promises and the advantage and when to ease await/async. One thing to keep in mind, if you return a Promise, it will follow the entire then chain, and it will always return a Promise with a value of the last then.. if that makes sense :)
There are a few problems. One is that you never resolve the initial Promise unless the else block is entered. Another is that you should return the recursive get_data call every time, so that it can be properly chained with the initial Promise. You may also consider avoiding the explicit promise construction antipattern - get_items already returns a Promise, so there's no need to construct another one (same for the inside of get_items, axios calls return Promises too).
You might consider a plain while loop, reassigning the next_url string until it's falsey:
function get_items (baseURL) {
const options = {
baseURL: url,
method: 'get'
}
// return the axios call, handle errors in the consumer instead:
return axios(options)
.then(res => res.data)
}
async function get_data() {
const output = []
let next_url = 'https://some_url.com/api/data'
try {
while (next_url) {
const response = await get_items(next_url);
output.push(...response.results)
next_url = response.next;
}
} catch (e) {
// handle errors *here*, perhaps
console.log(e)
}
return output;
}
Note that .catch will result in a Promise being converted from a rejected Promise to a resolved one - you don't want to .catch everywhere, because that will make it difficult for the caller to detect errors.
Another way of doing it is to not use async at all and just recursively return a promise:
const getItems = (url) =>
axios({
baseURL: url,
method: 'get',
}).then((response) => response.data);
const getData = (initialUrl) => {
const recur = (result, nextUrl) =>
!nextUrl
? Promise.resolve(result)
: getItems(nextUrl).then((data) =>
recur(result.concat([data.results]), data.next),
);
return recur([],initialUrl)
.catch(e=>Promise.reject(e.stack));//reject with error stack
};
As CertainPerformance noted; you don't need to catch at every level, if you want getData to reject with error.stack you only need to catch it once.
However; if you had 100 next urls and 99 of them were fine but only the last one failed would you like to reject in a way that keeps the results so far so you can try again?
If you do then the code could look something like this:
const getData = (initialUrl) => {
const recur = (result, nextUrl) =>
!nextUrl
? Promise.resolve(result)
: getItems(nextUrl)
.catch(e=>Promise.reject([e,result]))//reject with error and result so far
.then((data) =>
recur(result.concat([data.results]), data.next),
);
return recur([],initialUrl);//do not catch here, just let it reject with error and result
};