Getting .json is not a function on Promise.all w/fetch - javascript

Oh once again I have those Promise.all blues:( I have a function that makes an array of fetch call's from provided urls and then we want to retrieve data via a Promise.all and return array of reponses or better yet just return the promise to calling function. . The problem is this results in error w/console showing:
There was problem retrieving data. TypeError: r.json is not a function
The code for the function is :
const getLeagueLeaders = (url, params) => {
// First let's create the array of url's
let queryURLs = [];
params.forEach((param) => {
queryURLs.push(
fetch(`${url}${new URLSearchParams(param)}`, {
method: "get",
headers: {
Authorization:
"Basic ==",
},
}).then((res) => res.json())
);
});
return (
Promise.all(queryURLs)
// map array of responses into an array of response.json() to read their content
.then((responses) => responses.map((r) => r.json()))
.catch((err) => {
console.error("There was problem retrieving data.", err);
})
);
};
module.exports = getLeagueLeaders;
And in Vue component
mounted: async function () {
const leagueLeadersResponseArray = await getLeagueLeaders(
this.fetchBaseUrl,
this.params
);
this.qbLeaders =
leagueLeadersResponseArray[0].cumulativeplayerstats.playerstatsentry;
Obviously leagueLeadersResponseArray is undefined. I researched .json() and dont see how I am using it incorrectly. At first i thought I needed a Promise.all wrapper for the responses.map((r) => r.json()) but that did no good either. I looked at this link but I am not using fetch as he is. Any guidance much appreciated....
Updated working code for anybody else:
// ---------- src/js/modules/ ------------------ //
/* jshint ignore:start */
// Make function to retrieve League Leaders in a Category
const getLeagueLeaders = (url, params) => {
// First let's create the array of url's
let queryURLs = [];
params.forEach((param) => {
queryURLs.push(
fetch(`${url}${new URLSearchParams(param)}`, {
method: "get",
headers: {
Authorization:
"Basic ==",
},
}).then((res) => res.json())
);
});
return Promise.all(queryURLs).catch((err) => {
console.error("There was problem retrieving data.", err);
});
};
module.exports = getLeagueLeaders;

Your template string is around the entire fetch when it should only be in the argument to fetch:
params.forEach((param) => {
queryURLs.push(fetch(`${url}${new URLSearchParams(param)}`, {
method: "get",
headers: {
Authorization:
"Basic *****==",
}
}));
});
Then, you have a .then(data => {return data}), which doesn't do anything since the return returns from the then callback, not the function. You should instead return the promise that Promise.all gives you:
return Promise.all(queryURLs)
// map array of responses into an array of response.json() to read their content
.then((responses) => responses.map((r) => r.json())) // Get error There was problem retrieving data. TypeError: r.json is not a function
.catch((err) => {
console.error("There was problem retrieving data.", err);
});

Related

How do I use a Get query?

Hello I created a form allowing me to choose which database table I want to observe. I then want to make a query based on the selected data but it seems that the format or my way of doing it does not seem good.
Here is my fetch function:
temp_select: 'temperature'
async exportData(){
const data_select = encodeURIComponent(this.temp_select);
const url = `http://192.168.1.51:3000/api/v1/export/${data_select}`;
fetch(url)
.then(res => res.text())
.then((result) => {
console.log(typeof(data_select));
const data = JSON.parse(result);
console.log(data);
})
.catch((error) => {
console.log(error)
});
},
and here is my query function
async function exportDatabase(req, res){
const data_selected = req.params.data_select;
return db.any('SELECT $1 FROM tag_7z8eq73', [data_selected])
.then(rows => {
res.json(rows)
})
.catch(error => {
console.log(error)
});
}
the database is loaded but here is what I observe
It works correctly in this form:
async function exportDatabase(req, res){
return db.any('SELECT temperature FROM tag_7z8eq73')
.then(rows => {
res.json(rows)
})
.catch(error => {
console.log(error)
}); }
I'm working with node.js and vue.js
Can someone enlighten me?
edit : #Quentin Thank you for your answer you help me understand how the GET request works. Here is my problem solved :
fetch function :
async exportData(){
const data_select = encodeURIComponent(this.temp_select);
const url = `http://192.168.1.51:3000/api/v1/export/${data_select}`;
fetch(url)
.then(res => res.text())
.then((result) => {
console.log(typeof(data_select));
const data = JSON.parse(result);
console.log(data);
})
.catch((error) => {
console.log(error)
});
},
Query function:
async function exportDatabase(req, res){
const data_selected = req.params.data_select;
return db.any('SELECT ' + data_selected + ' FROM tag_7z8eq73')
.then(rows => {
res.json(rows)
})
.catch(error => {
console.log(error)
}); }
my route :
router.get('/export/:data_select', db.exportDatabase);
method: 'GET',
You are making a GET request
headers: {
'Content-Type': 'application/json',
},
This claims that the request body is JSON.
GET requests cannot have a request body, so this cannot be true.
(Technically they can, but really shouldn't, but fetch won't let you give a GET request a body).
Remove this header.
params: JSON.stringify({
data_select: this.temp_select,
}),
fetch does not have a params option, so this is nonsense. Remove it.
async function exportDatabase(req, res){
return db.any('SELECT $1 FROM tag_7z8eq73', [req.params.data_select])
Assuming you are using Express, look at the definition of params:
This property is an object containing properties mapped to the named route “parameters”. For example, if you have the route /user/:name, then the “name” property is available as req.params.name. This object defaults to {}.
Which means you need to define data_select when you define the route:
app.get('/api/v1/export/:data_select', exportData);
And then put the value in the URL when you make the request:
const data_select = encodeURIComponent(this.temp_select);
const url = `http://192.168.1.51:3000/api/v1/export/${data_select}`;
fetch(url).then(...);

How can I write unit test for api call with token using React, Jest and React-testing-library?

Here is the function that I wanna test, it takes a token and a description as props. Normally in React code, I can get token from useContext.
export const updateUserProfileAbout = async (
token,
description
) => {
const dataUpdateTemplateDescriptionRes = await patchData(`me/`, token, {
about:description,
});
const dataUpdateTemplateDescriptionJson = await dataUpdateTemplateDescriptionRes.json();
return dataUpdateTemplateDescriptionJson;
};
And here is my custom patchData function:
const patchData = async (urn, token, data = "") => {
const headers = {
"Content-Type": "application/json",
Authorization: `Bearer ${token.access}`,
};
const body = data ? JSON.stringify(data) : null;
let response;
if (body) {
response = await fetch(`${host}/api/${urn}`, {
method: "PATCH",
headers,
body,
});
} else {
response = await fetch(`${host}/api/${urn}`, {
method: "PATCH",
headers,
});
}
if (!response.ok) throw new Error(response.status);
return response;
};
You are right. You don't need the token. All you need to do for mocking the fetch is the following:
jest.spyOn(global, 'fetch').mockImplementationOnce(
jest.fn(() => Promise.resolve()) as jest.Mock);
If you want to retrieve a specific object from a json response, you can use:
jest.spyOn(global, 'fetch').mockImplementationOnce(
jest.fn(() => Promise.resolve({ ok: true, json: () => Promise.resolve({ myObject }) })) as jest.Mock);
You can also reject it to trigger the error catch:
jest.spyOn(global, 'fetch').mockImplementationOnce(
jest.fn(() => Promise.reject()) as jest.Mock);
If you want to return something multiple times, change the mockImplementationOnce to whatever you need (maybe mockImplementation, for returning it every time you call it).
If you also want to expect the call of the fetch just add a constant:
const myFetch = jest.spyOn(global, 'fetch').mockImplementationOnce(
jest.fn(() => Promise.reject()) as jest.Mock);
You can then expect it via: expect(myFetch).toBecalledTimes(1);
After one more day of researching, I might be wrong though but I don't think I have to care about token or authorization when unit testing for front-end. All I need is jest.fn() to mock function and jest.spyOn(global, "fetch") to track fetch API.
For more information, here are some references that I read:
https://codewithhugo.com/jest-fn-spyon-stub-mock/
https://dev.to/qmenoret/mocks-and-spies-with-jest-32gf
https://www.pluralsight.com/guides/how-does-jest.fn()-work
https://www.loupetestware.com/post/mocking-api-calls-with-jest

Fetch Result Return with status code and json data

I have a fetch API call that calls API back end and in return, I will get the response object with status code. What I am trying to do is base on return, I wanted to return the JSON response with status code. so that other part of the javascript can manipulate base on status code. My fetch function is as follow.
I have tried with as follow below, but it returns as a given screenshot. It gives me promise value which I didn't want to get.
export const createUser = ( posts ) => {
const apiRoute= "/api/register";
return window.fetch(`${apiRoute}`, {
"headers": headers,
method: "POST",
body: JSON.stringify(posts)
}).then(response => ({
'status' : response.status,
'data' : response.json()
}))
.catch(error => console.error('Error: ', error))
;
}
I know that it might be the duplicate from this post (Fetch api - getting json body in both then and catch blocks for separate status codes), but I do not want my data to return as a promise. Instead, I wanted to return fully constructed well form JSON data.
Something like this.
{status: 400, data: {id:1,name:Hello World}}
how can i achieve this?
"It gives me promise value"
That's right, as per the documentation.
You need to resolve that promise before resolving the outer promise.
For example
.then(response => {
return response.json().then(data => ({
status: response.status,
data
}))
})
Alternatively, use an async function
export const createUser = async ( posts ) => {
const apiRoute= "/api/register";
try {
const response = await window.fetch(apiRoute, {
headers,
method: "POST",
body: JSON.stringify(posts)
})
return {
status: response.status,
data: await response.json()
}
} catch (error) {
console.error('Error: ', error)
throw error
}
}

fetch inside fetch error setState

I have a problem solving this react.js
loadFromServer(pageSize) {
fetch('http://localhost:8080/api/employees')
.then(response => {
return fetch('http://localhost:8080/api/profile/employees',
{
headers: new Headers({
'Accept': 'application/schema+json'
})
}).then(schema => {
this.scheme = schema;
return response.json();
}
)
})
.then(response =>
this.setState(
{
employees: response._embedded.employees,
attributes: Object.keys(this.scheme.json().properties),
pageSize: pageSize,
links: response._links}
)
);
}
at this part
attributes: Object.keys(this.scheme.json().properties),
always return (in promise) TypeError: Cannot convert undefined or null to object.
If I put console.log(this.scheme.json()) I can see the Promise but, why inside setState I get null object?
There are a few issues here:
The main one is that this.schema.json() returns a promise (as you know from your console.log). Promises don't have a properties property, so you're passing undefined to Object.keys, which then gives you that error.
You're also not checking for errors from fetch, in two different ways: You're not checking .ok (which is such a common error I've posted about it on my anemic little blog), and you're not checking for promise rejection.
You're also doing some unnecessary promise nesting and could be overlapping your fetch calls more.
First, since it seems you often fetch JSON, I'd suggest giving yourself a utility function for it:
function fetchJSON(...args) {
return fetch(...args)
.then(response => {
if (!response.ok) {
throw new Error('HTTP error ' + response.status);
}
return response.json();
});
}
Notice the .ok check.
Then, also in the "break the problem into smaller pieces" category, I'd have a fetchSchema function:
function fetchSchema(url) {
return fetchJSON(url, {
headers: new Headers({
'Accept': 'application/schema+json'
})
});
}
Then, loadFromServer can use Promise.all and destructuring to run the operations in parallel:
// (I assume this is in a `class` or object initializer, as it doesn't have `function` in front of it)
loadFromServer(pageSize) {
Promise.all(
fetchJSON('http://localhost:8080/api/employees'),
fetchSchema('http://localhost:8080/api/profile/employees')
)
.then(([empResponse, schema]) => {
this.schema = schema;
this.setState({
employees: empResponse._embedded.employees,
attributes: Object.keys(schema.properties),
pageSize: pageSize,
links: empResponse._links
})
)
.catch(error => {
// Do something with the error
});
}
Note the .catch, since you're not returning the promise from loadFromServer. (If you want to buck errors up the chain, add return in front of Promise.all and move the .catch to calling code.)
Side note: Your code used
this.scheme = schema;
Note that the property on the left is scheme (with a final e) but the variable is schema (with a final a). I think you meant schema and so I've included that change in the above, but if the property is really supposed to be this.scheme, you'll want to adjust that. Or if you don't need that property for anything other than the code in loadFromServer, remove that line entirely.
I think you should use Promise.all to run the two requests in parrallel and then retrieve the two responses (by the way response.json() returns a Promise, that's why you have an error in your code) :
loadFromServer(pageSize) {
Promise.all([
fetch('http://localhost:8080/api/employees')
.then(response => {
if (!response.ok) throw Error(response.statusText);
return response.json();
),
fetch('http://localhost:8080/api/profile/employees')
.then(response => {
if (!response.ok) throw Error(response.statusText);
return response.json();
),
]).then(responses => {
this.setState({
employees: responses[0]._embedded.employees,
attributes: Object.keys(responses[1].properties),
pageSize: pageSize,
links: responses[0]._links
})
}).catch(error => {...})
}
I think you need something like:
loadFromServer(pageSize) {
fetch('http://localhost:8080/api/employees')
.then(response => {
return fetch('http://localhost:8080/api/profile/employees', {
headers: new Headers({
'Accept': 'application/schema+json'
})
}).then(schema => {
schema.json().then(data => {
this.scheme = data
})
});
return response.json();
})
.then(response =>
this.setState({
employees: response._embedded.employees,
attributes: Object.keys(this.scheme.properties),
pageSize: pageSize,
links: response._links
})
);
}
Response json() method in Fetch API returns a promise. For this reason fetch requests should be consistently chained with .then(response => response.json()) to get a plain object.
Flattening promises may result in more reliable control flow. Since responses from both requests are used, this would require to either nest then callbacks or passing another response through then chain. async may be useful because it conveniently solves flattening problem:
async loadFromServer(pageSize) {
const employeesResponse = await fetch('http://localhost:8080/api/employees', {
headers: new Headers({ 'Accept': 'application/schema+json' })
});
const employees = await employeesResponse.json();
const schemeResponse = await fetch('http://localhost:8080/api/profile/employees', {
headers: new Headers({ 'Accept': 'application/schema+json' })
});
const scheme = await schemeResponse.json();
this.setState({
employees: employees._embedded.employees,
attributes: Object.keys(scheme.properties),
pageSize: pageSize,
links: response._links
});
}
Since requests don't depend on each other, they could be performed in parallel with Promise.all.
async loadFromServer(pageSize) {
const employeesPromise = fetch('http://localhost:8080/api/employees', {
headers: new Headers({ 'Accept': 'application/schema+json' })
})
.then(res => res.json());
const schemePromise = fetch('http://localhost:8080/api/profile/employees', {
headers: new Headers({ 'Accept': 'application/schema+json' })
})
.then(res => res.json());
const [employees, scheme] = await Promise.all([employeesPromise, schemePromise]);
this.setState({
employees: employees._embedded.employees,
attributes: Object.keys(scheme.properties),
pageSize: pageSize,
links: response._links
});
}

Using Promise.all to resolve fetch requests

I have an array of 4 request objects that I want to use the Fetch API on and get back promises. I then want to resolve each of these promises and get the values back.
Here is how I am building the request objects.
let requestsArray = urlArray.map((url) => {
let request = new Request(url, {
headers: new Headers({
'Content-Type': 'text/json'
}),
method: 'GET'
});
return request;
});
And here is how I am trying to use Promise.all()
Promise.all(requestsArray.map((request) => {
return fetch(request).then((response) => {
return response.json();
}).then((data) => {
return data;
});
})).then((values) => {
console.log(values);
});
The last console.log(values) doesn't print anything to the console. Am I using Promise.all() wrong?
I know the first request goes through, and when I run each request individually, it works fine. The only issue is when I try to run them concurrently.
I can't see any problems, for me it returns just fine: https://jsfiddle.net/np5bx03j/
However, this is a test with jsfiddles /echo/json URLs and not your original ones. I therefore would assume some error occured in your case.
I suggest adding a catch to log errors:
Promise.all(requestsArray.map((request) => {
return fetch(request).then((response) => {
return response.json();
}).then((data) => {
return data;
});
})).then((values) => {
console.log('values', values);
}).catch(console.error.bind(console));
EDIT: Just for the sake of completeness: I can't see any problems according to the API (MDN) or anything else either.
why map it twice? Let the request array return the actual promise from fetch.
let requestsArray = urlArray.map((url) => {
let request = new Request(url, {
headers: new Headers({
'Content-Type': 'text/json'
}),
method: 'GET'
});
return fetch(request).then(res => res.json());
});
Now you have array of promises. Which Promise.all takes in.
Promise.all(requestsArray).then(allResults => {
console.log(allResults)
})
Here's a jsfiddle that mocks this: https://jsfiddle.net/uu58t1jj/

Categories