fetch repeatedly returns pending promise - javascript

I need to request data from my REST server to populate my UI (frontend). In doing so, I need to request some data from my and other servers. One such request is to get a list of states (provinces). I use fetch() and .json() to do this.
I see there are a few questions on SO addressing this issue mentioned below (here, here and a few more), however attempting their solutions/suggestions did not solve my problem.
e.g.
fetch("http://localhost:3443/app/location/provinces").then(e => e.json()).then(console.log);
Naturally fetch() being a network operation returns a promise, or I can use it with await & async. Also, .json() is also a promise. Since then() is called by resolve(data) to proceed to the next then() and .catch(error) processes reject(error), the request above should make sense.
Problem:
However, on each call to:
fetch("http://localhost:3443/app/location/provinces")
.then(e => e.json())
.then(console.log)
.catch(console.warn);
I get a PromiseĀ {<pending>}. Since it is the case, I tried the same with an async/await impl and receive the same "error":
(async () => {
let l = await fetch("http://localhost:3443/app/location/provinces");
console.log(l);
})();
What am I doing wrong/missing?

Related

Why my promise based request returns Promise object on return and expected data in console.log [duplicate]

This question already has answers here:
Async function returning promise, instead of value
(3 answers)
Closed 7 months ago.
please check the below axios get request example to a random public api.
Intro
I would simply like to create a function which will execute get requests and return data, so I can store the returned data in a variable and use it in the UI I am building.
Currently the function returns Promise object and not data as expected.
I also noticed that returned promise object is printed before logged data response. This makes me think that for some reason return command doesn't await the resolution of the promise.
I can use any other library apart axios as well, but tried fetch which is giving me same results.
Furthermore, I tried executing correct code from other stackoverflow answers on this topic, but it gave me same results.
Which leads me thinking my issue has something to do with my environment.
What I already tried
I looked into all answers on query "axios request returns undefined" and similar
I looked into all answers on query "get data from axios request" and similar
tried executing the code from browser using react
changing the function to synchronous, which instead of Promise object return undefined
My env:
System Version: macOS Monterey 12.4 (21F79)
Kernel Version: Darwin 21.5.0
Node#v18.7.0
axios#0.21.4
iterm#2
Example
The example can be executed by running node script.js.
// filename: script.js
const axios = require("axios");
async function fetchApi() {
const getRequest = await axios.get("https://api.publicapis.org/entries")
.then((response) => {
console.log("logged response:", response.data.count);
return response.data.count;
})
};
const response = fetchApi();
console.log("returned response:", response);
// execute#bash: node script.js
// returned response: Promise { <pending> }
// logged response: 1425
My question
I would expect returned result and logged result to be the same.
Please help me troubleshoot this issue.
It is impossible to return the data, because the data doesn't exist yet. Instead, you will need to return a promise. async functions automatically return promises, so you're part way there, but you currently aren't returning anything from your async function. I also recommend you don't mix async/await with .then as it will just cause confusion:
async function fetchApi() {
const response = await axios.get("https://api.publicapis.org/entries")
return response.data.count;
};
To access the eventual value of the promise, you will need to put your code in an async function and await the promise:
async function someFunction() {
const count = await fetchApi();
console.log(count);
}
Your fetchApi is an async function, which means it will return a Promise.
You should use either await or .then() syntax to execute your next actions after the Promise is resolved.
The only problem is that you can't use await syntax in your current implementation since you're using the result in the top level of your script file and not inside a function, Node didn't support top-level awaits in older versions.
So you should either use .then() syntax like this:
fetchApi().then( response => {
console.log("returned response:", response)
};
or wrap your code inside a function if you want to use await syntax:
async function main() {
const response = await fetchApi();
console.log("returned response:", response);
}
main();
if you are using Node v14.8+ you can just use await inside a module:
const response = await fetchApi();
console.log("returned response:", response);

Cancel previous promise

I am implementing a search input in my React app which may take several seconds for a response from the server based on the size of the result set. The issue I am having right now is if the user searches for a term which has a large result set and then searches for a term with a small result set straight after, the first search is returning after the last search and is overwriting the data, therefore displaying incorrect results on the table.
Currently I have a search component which calls an async function in its parent (this function can be called from several child components, Search being one of them). Inside the async function I have an await call to the service with the search query. Once that returns the results are passed to a function which updates some state.
I have read about cancel tokens but i'm not totally sure how to implement this. When the search component makes the initial call, there will be a promise which will be pending until the result is received. When I search again, how would I be able to ignore the result of the first promise? Each time I search, would I store the promise in a field of the component and somehow check this field in future searches?
I read many possible solutions to this online. I am using fetch-retry to handle the API call and would rather not use a library such as bluebird or axios. The main part I don't understand is how to have my promise not resolve if a promise has been created in future.
Hope I explained this well enough!
Thanks
When I search again, how would I be able to ignore the result of the first promise?
You probably don't want that, as youbare wasting some bandwith if you do a request and ignore its result. Instead you should cancel the underlying request (not the promise, promises can't be canceled directly).
To do so you could keep an abortion controller for each search:
this.controller = new AbortController();
Then launch all fetch requests as:
fetch(url, { signal: this.controller.signal })
.then(res => res.json())
.then(res => this.setState({ /*...*/ }))
.catch(() => /*..*/)
Now if you restart the search just do:
this.controller.abort();
this.controller = new AbortController();
and do the fetching again.
Read on

Can I somehow get the fetch response in the first `then`?

I am trying to use the Fetch API. It seems from examples that a GET request needs one then to parse the response somehow.
Currently I am doing this
fetch(url)
.then(response => response.json())
.then(response => {
console.log(response);
});
However that first then seems like a boilerplate. I tried to avoid it, for example:
fetch(url)
.then(response => {
console.log(response.json());
});
But this logs me a pending Promise with status resolved.
I read other questions on this topic and read a bit about promises, but I couldn't understand if it's possible to combine it in a single then (if so - how?) or not.
For example, two of the answers here point out that
There is no need to use more than one '.then'
and
there is no good reason to have two .then() handlers as the code from each could have been combined into a single .then() handler
But I couldn't get that example to actually work - I still got a promise :)
On the contrary, the accepted anwser here explains that .then actually does something to the result (extracts the returned from promise), but I couldn't unserstand if I can somehow do that myself, say response.json().then() or response.json().getVal() or is the double-then syntax the only way.
It's quite simple: when you dispatch the a fetch() request, it returns a promise containing the response. That is resolved by the first .then(). Resolving this first promise actually returns Response.
Now this is the tricky part: the methods that read the body of the response, be it .json(), .text(), .blob().... all return promises. This means that you will need to resolve the second promise in order to get the parsed response.
The flow looks like this:
Make a fetch() request, and it returns a Promise of type Response
When you attempt to resolve the content of the Response, it will return a second Promise, whose type depends on the method you use (e.g. .json() returns an object, .text() returns string, .blob() returns Blob).
Resolve the second Promise, and you get your actual parsed response body
p/s: If you're not using fetch() in a top-level context (as of the time of writing top-level await is still not a thing), then you can use async/await to make your code a little more readable:
const response = await fetch(url);
const content = await response.json();
console.log(content);
The first promise returned by fetch can be useful in some cases. If you want to avoid boilerplate, you could simply create your own function:
function fetch_json(url, opts) {
return fetch(url, opts)
.then(resp => resp.json());
}
fetch_json(your_url)
.then(json => console.log(json));
These days I am using the async/await syntax which is the same thing, but looks less like a boilerplate to me.
const response = await fetch(url)
const data = await response.json()
console.log(data)

How to access values outside of axios request?

Very easy question. When I run console.log on the jsonPayload in line 6, I see the expected output. When I run console.log again on jsonPayload in the last line, it returns an empty {}. How do I access the payload outside of the initial request?
var jsonPayload = {}
axios.get('http://localhost:8080/api/tools')
.then(function (response) {
jsonPayload = response.data[0]
// this returns the expected payload
console.log(jsonPayload)
})
.catch(function (error) {
console.log(error)
})
// this returns empty {}
console.log(jsonPayload)
I am not sure why you would like to access the jsonPayload outside of the axios get call, but I do know why you are receiving a log of {} from the outside console.log().
axios.get()
The above method will return a promise. This promise allows you to protect your next process from receiving empty data.
Inside the .then() method, you are able to retrieve the data that is sent back to you from the axios.get() call. However, this data cannot reach you until the process of retrieving data from the API, is complete.
This is where you are running into your issue. The console.log() outside of the .then() method is triggering before the completion of the ajax call.
You could also look into using ES7 async and await. This allows you to write these axios calls without the .then().
You cannot do this because the request is processed asynchronously.
As #d3L answered, you're limited to handle the response within the callback passed to .then. However, after ES8 you can use async / await which is an alternative to using typical Promise handling with callbacks. Is still asynchronous, but looks synchronous:
(async function() {
try {
const response = await axios.get('http://localhost:8080/api/tools');
console.log(response.data[0])
} catch(err) {
console.log(err);
}
})();
Note whether you're running it on node or the browser, the engine might not support it, so you might need to transpile the code using babel.

Why is my array of Promises running before calling Promise.all()?

I am trying to create an array of Promises, then resolve them with Promise.all(). I am using got, which returns a promise.
My code works, but I don't fully understand how. Here it is:
const got = require('got');
const url = 'myUrl';
const params = ['param1', 'param2', 'param3'];
let promiseArray = [];
for (param of params) {
promiseArray.push(got(url + param));
}
// Inspect the promises
for (promise of promiseArray) {
console.log(JSON.stringify(promise));
// Output: promise: {"_pending":true,"_canceled":false,"_promise":{}}
}
Promise.all(promiseArray).then((results) => {
// Operate on results - works just fine
}).catch((e) => {
// Error handling logic
});
What throws me off is that the Promises are marked as "pending" when I add them into the array, which means they have already started.
I would think that they should lie inactive in promiseArray, and Promise.all(promiseArray) would both start them and resolve them.
Does this mean I am starting them twice?
You're not starting them twice. Promises start running as soon as they're created - or as soon as the JS engine finds enough resources to start them. You have no control on when they actually start.
All Promise.all() does is wait for all of them to settle (resolve or reject). Promise.all() does not interfere with nor influence the order/timing of execution of the promise itself.
Promises don't run at all. They are simply a notification system for communicating when asynchronous operations are complete.
So, as soon as you ran this:
promiseArray.push(got(url + param));
Your asynchronous operation inside of got() is already started and when it finishes, it will communicate that back through the promise.
All Promise.all() does is monitor all the promises and tell you when the first one rejects or when all of them have completed successfully. It does not "control" the async operations in any way. Instead, you start the async operations and they communicate back through the promises. You control when you started the async operations and the async operations then run themselves from then on.
If you break down your code a bit into pieces, here's what happens in each piece:
let promiseArray = [];
for (param of params) {
promiseArray.push(got(url + param));
}
This calls got() a bunch of times starting whatever async operation is in that function. got() presumably returns a promise object which is then put into your promiseArray. So, at this point, the async operations are all started already and running on their own.
// Inspect the promises
for (promise of promiseArray) {
console.log(JSON.stringify(promise));
// Output: promise: {"_pending":true,"_canceled":false,"_promise":{}}
}
This loop, just looks at all the promises to see if any of them might already be resolved, though one would not expect them to be because their underlying async operations were just started in the prior loop.
Promise.all(promiseArray).then((results) => {
// Operate on results - works just fine
}).catch((e) => {
// Error handling logic
});
Then, with Promise.all(), you're just asking to monitor the array of promises so it will tell you when either there's a rejected promise or when all of them complete successfully.
Promises "start" when they are created, i.e. the function that gives you the promise, has already launched the (often asynchronous) operations that will eventually lead into an asynchronous result. For instance, if a function returns a promise for a result of an HTTP request, it has already launched that HTTP request when returning you the promise object.
No matter what you do or not do with that promise object, that function (got) has already created a callback function which it passed on to an asynchronous API, such as a HTTP Request/Response API. In that callback function (which you do not see unless you inspect the source of got) the promise will be resolved as soon as it gets called back by that API. In the HTTP request example, the API calls that particular callback with the HTTP response, and the said callback function then resolve the promise.
Given all this, it is a bit strange to think of promises as things that "start" or "run". They are merely created in a pending state. The remaining thing is a pending callback from some API that will hopefully occur and then will change the state of the promise object, triggering then callbacks.
Please note that fetching an array of urls with Promise.all has got some possible problems:
If any of the urls fail to fetch your resolve is never called (so
one fails and your resolve function is never called.
If your array is very large you will clobber the site and your network with requests, you may want to throttle the maximum open requests and or requests made in a certain timeframe.
The first problem is easily solved, you process the failed requests and add them to the results. In the resolve handler you can decide what to do with the failed requests:
const got = require('got');
const url = 'myUrl';
const params = ['param1', 'param2', 'param3'];
const Fail = function(details){this.details = details;};
Promise.all(
params.map(
param =>
got(url + param)
.then(
x=>x,//if resolved just pass along the value
reject=>new Fail([reject,url+param])
)
)
).then((results) => {
const successes = results.filter(result=>(result && result.constructor)!==Fail),
const failedItems = results.filter(result=>(result && result.constructor)===Fail);
}).catch((e) => {
// Error handling logic
});
Point 2 is a bit more complicated, throttling can be done with this helper function and would look something like this:
... other code
const max5 = throttle(5);
Promise.all(
params.map(
param =>
max5(got)(url + param)
.then(
x=>x,//if resulved just pass along the value
reject=>new Fail([reject,url+param])
)
)
)

Categories

Resources