Using Promise all to fill dropdowns - javascript

I'm using Promises to automatically fill up my dropdowns on page load (I have multiple dropdowns on the page).
Here is the code I use to return the following:
$(document).ready(function(){
var urls = ['getBrands', 'getTags'];
Promise.all(urls.map(u=>fetch(u))).then(
responses => Promise.all(responses.map(res => res.json()))
).then(
texts=>console.log(texts)
).then(
result => console.log(result[0]) //This is where the error is
)
});
This prints the response to the console correctly, but throws an error when I try to read the individual result. The error is Uncaught(in promise) TypeError: cannot read property '0' of undefined

The problem is your first fulfillment handler returns undefined, which becomes the fulfillment value of the promise it returns.
If you just remove it, your second fulfillment handler will see the values.
$(document).ready(function(){
var urls = ['getBrands', 'getTags'];
Promise.all(urls.map(u=>fetch(u))).then(
responses => Promise.all(responses.map(res => res.json()))
).then(
result => console.log(result[0])
)
});
Alternatively, have it return what it receives:
$(document).ready(function(){
var urls = ['getBrands', 'getTags'];
Promise.all(urls.map(u=>fetch(u))).then(
responses => Promise.all(responses.map(res => res.json()))
).then(texts => {
console.log(texts);
return texts;
}).then(
result => console.log(result[0])
)
});
Side note: That code breaks one of the Rules of Promises, which is:
Handle rejection, or pass the promise chain to something that will.
You probably want to add a rejection handler via .catch.
Side note 2: Assuming fetch is the standard fetch, your code is missing a check for success. This is a footgun in the fetch API (I write about it here). Fixing it:
Promise.all(
urls.map(u=>fetch(u).then(response => {
if (!response.ok) {
throw new Error("HTTP error " + response.status);
}
return res.json();
}))
).then(texts => {
Note that that eliminates the need for the second Promise.all as well, but handling each fetch individually earlier.

Related

Why `response.json().<key>` returns unidentified value

I'm trying to fetch a json file the api in the code below ,When I convert the json response to a javascript object I expect to be able to access the value of the key results in that object which is an array but when i log the response in the second then chain i get unidentified
const URLS = "https://pokeapi.co/api/v2/pokemon?limit=1000"
fetch(URLS)
.then(response => {
return response.json().results
})
.then((pokemons) => {
console.log(typeof pokemons)
});
When I changed the code to this
const URLS = "https://pokeapi.co/api/v2/pokemon?limit=1000"
fetch(URLS)
.then(response => {
return response.json()
})
.then((pokemons) => {
console.log(typeof pokemons.results)
});
it seems working fine but i don't understand why this problem is happening
response.json() returns a Promise resolving to the result of parsing the text of the response as JSON. You cannot directly access properties on that result without awaiting the Promise with .then or await in an async function.

When Response.text() promise will reject?

I see that on MDN/Response/text docs show the example of using .text() only with then
response.text().then(function (text) {
// do something with the text response
});
It returns a promise that resolves with a String.
Because of lint rules, I need to put
// eslint-disable-next-line #typescript-eslint/no-floating-promises
res.text().then(async (t) => {
Is there a use case when I need to catch a rejected promise from Response.text()? Maybe some examples?
It can fail/reject if the response was already consumed/read, so i.e if something already called .text/.json etc on it.
Looking at the polyfill implementation (https://github.com/github/fetch/blob/d1d09fb8039b4b8c7f2f5d6c844ea72d8a3cefe6/fetch.js#L301 ) I don't see other likely cases though.
Example:
response.text()
.then(t1 => {
console.log({ t1 }); // after calling text() we can see the result here
return response; // but we decided to return the response to the next handler
})
.then(res =>res.text()) // here we try to read text() again
.then(t2 => console.log({ t2 })) // and expecting text to be logged here
.catch(er => console.log({ er })); // but the text() promise rejects with
// TypeError: Failed to execute 'text' on 'Response': body stream already read

The catch clause in Promise.all doesn't work

I'm trying to load bunch of text files using multiple fetch requests via Promise.all. Here's how it looks:
////////////////////////////////////////
const loadTextFiles = (allUrls, textUrls) => {
const requests = textUrls.map(url => fetch(url));
return Promise.all(requests)
.then(responses => Promise.all(
responses.map(response => response.text())
))
.then(texts => texts.forEach(
(text, i) => allUrls[textUrls[i]] = text
))
.catch(err => {
// log("HERE");
throw exception("networkLoader",
"loadTextFiles",
`error in loadTextFiles: ${err.toString()}`);
});
};
allUrls and textUrls are two arrays that contain the urls of all resources (files with any extension) and text resources (files with .txt extension), respectively. In fact, textUrls is constructed from allUrls (never mind why or how). The results are stored inside the allUrls array. For example, if allUrls = [ "image.png", "essay.txt" ] , then textUrls = [ "essay.txt" ] . Now, if I call loadTextFiles:
await loadTextFiles(allUrls, textUrls);
I will get access to the contents of "essay.txt" by accessing allUrls["essay.txt"]. Things are okay so long as all text files exist and can be retrieved.
The problem is, despite the fact that I have written a catch to detect problems in Promise.all, it doesn't work. For instance, if I request fileThatDoesNotExist.txt I get a 404 (Not Found) in the browser console but my code inside catch doesn't run. I tested it even with a custom log function which also doesn't run. I'd like to catch the error and re-throw it. How do I do that?
Edit: When I said "I'd like to catch the error" I meant that I want to get notified of 404 error and throw an exception (in this case a custom exception object)
This question can be solved in the following way:
const loadTextFiles = (allUrls, textUrls) => {
const requests = textUrls.map(url => fetch(url));
return Promise.all(requests)
.then(responses => Promise.all(
responses.map(response => {
if (response.ok) {
return response.text();
}
throw exception("networkLoader",
"loadTextFiles",
`couldn't load ${response.url}`);
})
))
.then(texts => texts.forEach(
(text, i) => allUrls[textUrls[i]] = text
))
.catch(err => {
if (exception.isException(err)) {
throw err;
}
throw exception("networkLoader",
"loadTextFiles",
`error: ${err.toString()}`);
});
};
We test each response returned by fetch by checking its ok property, and if this property is false, then an exception is thrown.

Returning data from Axios API [duplicate]

This question already has answers here:
How do I return the response from an asynchronous call?
(41 answers)
Closed 3 months ago.
I am trying to use a Node.JS application to make and receive API requests. It does a get request to another server using Axios with data it receives from an API call it receives. The second snippet is when the script returns the data from the call in. It will actually take it and write to the console, but it won't send it back in the second API.
function axiosTest() {
axios.get(url)
.then(function (response) {
console.log(response.data);
// I need this data here ^^
return response.data;
})
.catch(function (error) {
console.log(error);
});
}
...
axiosTestResult = axiosTest();
response.json({message: "Request received!", data: axiosTestResult});
I'm aware this is wrong, I'm just trying to find a way to make it work. The only way I can seem to get data out of it is through console.log, which isn't helpful in my situation.
The issue is that the original axiosTest() function isn't returning the promise. Here's an extended explanation for clarity:
function axiosTest() {
// create a promise for the axios request
const promise = axios.get(url)
// using .then, create a new promise which extracts the data
const dataPromise = promise.then((response) => response.data)
// return it
return dataPromise
}
// now we can use that data from the outside!
axiosTest()
.then(data => {
response.json({ message: 'Request received!', data })
})
.catch(err => console.log(err))
The function can be written more succinctly:
function axiosTest() {
return axios.get(url).then(response => response.data)
}
Or with async/await:
async function axiosTest() {
const response = await axios.get(url)
return response.data
}
Guide on using promises
Info on async functions
I know this post is old. But i have seen several attempts of guys trying to answer using async and await but getting it wrong. This should clear it up for any new references
UPDATE: May 2022
This answer is still having lots of interest and have updated it to use arrow functions
const axiosTest = async () {
try {
const {data:response} = await axios.get(url) //use data destructuring to get data from the promise object
return response
}
catch (error) {
console.log(error);
}
}
you can populate the data you want with a simple callback function,
let's say we have a list named lst that we want to populate,
we have a function that pupulates pupulates list,
const lst = [];
const populateData = (data) => {lst.push(data)}
now we can pass the callback function to the function which is making the axios call and we can pupulate the list when we get data from response.
now we make our function that makes the request and pass populateData as a callback function.
function axiosTest (populateData) {
axios.get(url)
.then(function(response){
populateData(response.data);
})
.catch(function(error){
console.log(error);
});
}
The axios library creates a Promise() object. Promise is a built-in object in JavaScript ES6. When this object is instantiated using the new keyword, it takes a function as an argument. This single function in turn takes two arguments, each of which are also functions — resolve and reject.
Promises execute the client side code and, due to cool Javascript asynchronous flow, could eventually resolve one or two things, that resolution (generally considered to be a semantically equivalent to a Promise's success), or that rejection (widely considered to be an erroneous resolution). For instance, we can hold a reference to some Promise object which comprises a function that will eventually return a response object (that would be contained in the Promise object). So one way we could use such a promise is wait for the promise to resolve to some kind of response.
You might raise we don't want to be waiting seconds or so for our API to return a call! We want our UI to be able to do things while waiting for the API response. Failing that we would have a very slow user interface. So how do we handle this problem?
Well a Promise is asynchronous. In a standard implementation of engines responsible for executing Javascript code (such as Node, or the common browser) it will resolve in another process while we don't know in advance what the result of the promise will be. A usual strategy is to then send our functions (i.e. a React setState function for a class) to the promise, resolved depending on some kind of condition (dependent on our choice of library). This will result in our local Javascript objects being updated based on promise resolution. So instead of getters and setters (in traditional OOP) you can think of functions that you might send to your asynchronous methods.
I'll use Fetch in this example so you can try to understand what's going on in the promise and see if you can replicate my ideas within your axios code. Fetch is basically similar to axios without the innate JSON conversion, and has a different flow for resolving promises (which you should refer to the axios documentation to learn).
GetCache.js
const base_endpoint = BaseEndpoint + "cache/";
// Default function is going to take a selection, date, and a callback to execute.
// We're going to call the base endpoint and selection string passed to the original function.
// This will make our endpoint.
export default (selection, date, callback) => {
fetch(base_endpoint + selection + "/" + date)
// If the response is not within a 500 (according to Fetch docs) our promise object
// will _eventually_ resolve to a response.
.then(res => {
// Lets check the status of the response to make sure it's good.
if (res.status >= 400 && res.status < 600) {
throw new Error("Bad response");
}
// Let's also check the headers to make sure that the server "reckons" its serving
//up json
if (!res.headers.get("content-type").includes("application/json")) {
throw new TypeError("Response not JSON");
}
return res.json();
})
// Fulfilling these conditions lets return the data. But how do we get it out of the promise?
.then(data => {
// Using the function we passed to our original function silly! Since we've error
// handled above, we're ready to pass the response data as a callback.
callback(data);
})
// Fetch's promise will throw an error by default if the webserver returns a 500
// response (as notified by the response code in the HTTP header).
.catch(err => console.error(err));
};
Now we've written our GetCache method, lets see what it looks like to update a React component's state as an example...
Some React Component.jsx
// Make sure you import GetCache from GetCache.js!
resolveData() {
const { mySelection, date } = this.state; // We could also use props or pass to the function to acquire our selection and date.
const setData = data => {
this.setState({
data: data,
loading: false
// We could set loading to true and display a wee spinner
// while waiting for our response data,
// or rely on the local state of data being null.
});
};
GetCache("mySelelection", date, setData);
}
Ultimately, you don't "return" data as such, I mean you can but it's more idiomatic to change your way of thinking... Now we are sending data to asynchronous methods.
Happy Coding!
axiosTest() needs to return axios.get, which in turn returns a Promise.
From there, then can be used to execute a function when said Promise resolves.
See Promise for more info.
Alternatively, await can be used from within the scope of some async function.
// Dummy Url.
const url = 'https://jsonplaceholder.typicode.com/posts/1'
// Axios Test.
const axiosTest = axios.get
// Axios Test Data.
axiosTest(url).then(function(axiosTestResult) {
console.log('response.JSON:', {
message: 'Request received',
data: axiosTestResult.data
})
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.18.0/axios.js"></script>
IMO extremely important rule of thumb for your client side js code is to keep separated the data handling and ui building logic into different funcs, which is also valid for axios data fetching ... in this way your control flow and error handlings will be much more simple and easier to manage, as it could be seen from this
ok fetch
and this
NOK fetch
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script>
function getUrlParams (){
var url_params = new URLSearchParams();
if( window.location.toString().indexOf("?") != -1) {
var href_part = window.location.search.split('?')[1]
href_part.replace(/([^=&]+)=([^&]*)/g,
function(m, key, value) {
var attr = decodeURIComponent(key)
var val = decodeURIComponent(value)
url_params.append(attr,val);
});
}
// for(var pair of url_params.entries()) { consolas.log(pair[0]+ '->'+ pair[1]); }
return url_params ;
}
function getServerData (url, urlParams ){
if ( typeof url_params == "undefined" ) { urlParams = getUrlParams() }
return axios.get(url , { params: urlParams } )
.then(response => {
return response ;
})
.catch(function(error) {
console.error ( error )
return error.response;
})
}
// Action !!!
getServerData(url , url_params)
.then( response => {
if ( response.status === 204 ) {
var warningMsg = response.statusText
console.warn ( warningMsg )
return
} else if ( response.status === 404 || response.status === 400) {
var errorMsg = response.statusText // + ": " + response.data.msg // this is my api
console.error( errorMsg )
return ;
} else {
var data = response.data
var dataType = (typeof data)
if ( dataType === 'undefined' ) {
var msg = 'unexpected error occurred while fetching data !!!'
// pass here to the ui change method the msg aka
// showMyMsg ( msg , "error")
} else {
var items = data.dat // obs this is my api aka "dat" attribute - that is whatever happens to be your json key to get the data from
// call here the ui building method
// BuildList ( items )
}
return
}
})
</script>
After 6 hours of fluttering, I realized it was a one-line problem. If you are interfering with the axios life-cycle, you may have forgotten this line:
componentDidMount() {
this.requestInterceptor = axios.interceptors.request.use((request) => {
this.updateApiCallFor(request.url, true);
return request;
});
this.responseInterceptor = axios.interceptors.response.use((response) => {
this.updateApiCallFor(response.config.url, false);
return response; // THIS LINE IS IMPORTANT !
}, (error) => {
this.updateApiCallFor(error.config.url, false);
throw error;
});
async makes a function return a Promise
await makes a function wait for a Promise
code async/await
// https://www.npmjs.com/package/axios
const axios = require('axios')
/* --- */
async function axiosTest() {
let promiseAxios = axios.get( 'https://example.com' )
/* --- */
console.log( await promiseAxios )
}
/* --- */
axiosTest()
replit.com Stackoverflow - Returning data from Axios API
replit.com Stackoverflow - How to return values from async
code async/await with return
// https://www.npmjs.com/package/axios
const axios = require('axios')
/* --- */
async function axiosTest() {
console.log( await promiseAxios() )
}
/* --- */
axiosTest()
/* --- */
// create function for promise axios and return it
function promiseAxios() {
return axios.get( 'https://example.com' )
}
replit.com Stackoverflow - Returning data from Axios API - return
replit.com Stackoverflow - How to return values from async - return
Try this,
function axiosTest() {
axios.get(url)
.then(response => response.data)
.catch(error => error);
}
async function getResponse () {
const response = await axiosTest();
console.log(response);
}
getResponse()
It works, but each function where you want to get the response needs to be an async function or use an additional .then() callback.
function axiosTest() {
axios.get(url)
.then(response => response.data)
.catch(error => error);
}
async function getResponse () {
axiosTest().then(response => {
console.log(response)
});
}
getResponse()
If anyone knows a way to avoid this please do tell.
Also checkout Katsiaryna (Kate) Lupachova's article on Dev.to. I think it will help.
async handleResponse(){
const result = await this.axiosTest();
}
async axiosTest () {
return await axios.get(url)
.then(function (response) {
console.log(response.data);
return response.data;})
.catch(function (error) {
console.log(error);
});
}
You can find check https://flaviocopes.com/axios/#post-requests url and find some relevant information in the GET section of this post.
You can use Async - Await:
async function axiosTest() {
const response = await axios.get(url);
const data = await response.json();
}

Node js Aggregate response of 10 async calls

We have a requirement where we want to build api calls based on query param array for (contentId[]=1&contentId[]=2....and so on ), and then make async calls appending id to the api end point e.g http://xxxx/content/contentId.
Based on the response we need to aggregate and wrap the content with fields contentId, responsecode that we receive when we hit the individual api endpoints
{
{ "contentd": "1",
"responsecode": 200,
"messsage": "",
content{
}
}
{ "contentd": "2",
"responsecode": 200,
"messsage": "",
content{
}
...
}
We are using promise to do the same. I used promise all as below.
Promise.all(req.query.contentId
.map(function (contentId) {
console.log('processing the content id'+contentId);
return getContent(contentId);
}))
.then(function (all_content) {
//convert each resolved promised into JSON and convert it into an array.
res.json(all_content.map(function (content) {
return JSON.parse(content)
}));
})
.catch(function(rej) {
//here when you reject the promise
console.log("404 received"+rej);
}) ;
} catch (e) {
res.send(e.message);
}
});
// this is the function that makes the call to the backend. Note usage of request promise
function getContent(contentId) {
console.log('content id received to fetch the content'+contentId);
var options = {
uri: 'http://xxxxx/api/content/'+contentId,
headers: {
'Authorization': 'Basic YWRtaW46YWRtaW4='
},
qs: {
_format: 'json'
}
};
return rp(options);
}
Problems -The problem we are facing is that for the calls when we get http status code as 200 the result comes fine. However, for http status code 404(not found) the processing is not done. It seems to fail fast.
Also, how do I insert own fields for response, status while processing the content response as above in JSON.parse. thanks
If you read about Promise.all(), that's what it does. If any promise you pass it rejects, it immediately rejects the overall Promise.all() promise and you don't get the other results.
If you want all the other results, you have to either catch any rejections earlier so that Promise.all() never sees them or use a different way of tracking all the promises that is designed to get all respones, whether any reject or not.
You can catch the rejection earlier to "hide" it form Promise.all() by doing something like this and you also have to skip the JSON.parse() of any error objects that are passed through.
Promise.all(req.query.contentId
.map(function (contentId) {
console.log('processing the content id'+contentId);
// catch errors here and make the resolved value be the error object
// so Promise.all() processing continues on the other requests
return getContent(contentId).catch(e => e);
}))
.then(function (all_content) {
//convert each resolved promised into JSON and convert it into an array.
res.json(all_content.map(function (content) {
// make sure we don't try to parse things that are already objects
// like error objects
if (typeof content !== "string") {
return content;
} else {
return JSON.parse(content);
}
}));
}).then(function(results) {
// remove any error objects
// and probably log them to so they aren't just silently eaten
return results.filter(item => !(item instanceof Error));
})
Then, later down stream while using this array of results, you need to skip any error objects in the results.
Alternatively, you could make the results into some sort of no-op result that wouldn't affect your downstream processing or you can build a different array that had all error results filtered out of it.
And, if you want some prewritten code that just gets all the responses (including error results), that is often called Promise.settle(). There are several implementations of a Promise.settle() that you can use here:
ES6 Promise.all() error handle - Is .settle() needed?
Also, how do I insert own fields for response, status while processing the content response as above in JSON.parse.
You can change this:
return JSON.parse(content);
to something like this:
let obj = JSON.parse(content);
obj.someOtherProp = 'myvalue';
return obj;
return getContent(contentId);
Here you return the promise to Promise.all. so if the promise fails, it will trigger the Promise.all catch and all routes fail. So we need to catch earlier, so right there:
return getContent(contentId).catch( e => e);
That means in case of an error, proceed with the error as result. As the JSON.parse would fail then, you could return a json string instead:
return getContent(contentId).catch( e => '{"error":500}');
For the second question:
You may want to assign additional properties to the returned result:
return Object.assign( JSON.parse(content), {
what: "ever"
});

Categories

Resources