Fetch recursion using javascript to call Google place API - javascript

I need to get a list of results from a Google place API request. The API allows 20 results per page ans I need all results available so I need to go to the next page.The next page is accessible from a token given in the response of the previous request.
I've implemented the code below:
function request(url){
return fetch(url)
.then((response) => response.json())
.catch((error) => console.log(error))
}
This is my recursive function:
export function getListOfActivitiesInACity(city_name,nextPageToken,datas){
const first_url = 'https://maps.googleapis.com/maps/api/place/textsearch/json?query=activity+in+'+city_name+'&key='+ API_TOKEN +'&language=fr'
const next_url = 'https://maps.googleapis.com/maps/api/place/textsearch/json?pagetoken='+nextPageToken+'&key='+ API_TOKEN
var url = nextPageToken ===''? first_url : next_url;
return request(url)
.then((data) => {
const newData = [...datas, data];
if(data["next_page_token"] !== undefined){
return getListOfActivitiesInACity(city_name,data["next_page_token"],newData);
}
return newData;
})
}
And then I call my function and print results
var datas=[];
getListOfActivitiesInACity("Lyon",'',datas)
.then(data => {console.log(data);})
The first iteration of the fetch works fine and gives me the good new url for the next fetch
(I tried it on my broser directy and it works)
But the second fetch return me this :
Object {
"html_attributions": Array [],
"results": Array [],
"status": "INVALID_REQUEST",
}
I really don't understand why it doesn't work , so please can anyone help Thanks

Related

React Native API FETCH Different names for each objects

I am connecting a REST api from React Native app. I have Json response with filename objects with different names but all the objects have same variables: filename, message, and display.
Number of objects changes with each request to API (REST), the names of objects in response are different depending on requests. But the variables in each object are same as above.
The information I need from this response is only filename text, but it will be acceptable if I get list of objects so I can read through the messages from errors.
The image shows how my objects look like.
This is my fetch request :
const getGists = async () => {
await axios
.get(`https://api.github.com/gists/public?per_page=30`)
.then((r) => {
let n;
for (n = 0; n < 30; n++) {
console.log(r.data[n].files.filename);
// console.log("____________________");
// console.log(r.data[n].owner.avatar_url);
// console.log("____________________");
// console.log(JSON.stringify(r.data[n].files));
}
})
.catch((e) => {
console.log("ERROR", e);
});
};
how is possible to get every filename from these requests even if object name is not the same in each iteration . Thanks for help
Working with the result of the API calls and some higher-order functions, this will work fine:
const getGists = async () => {
await axios
.get(`https://api.github.com/gists/public?per_page=30`)
.then((response) => {
const myDesireResult = response.data.reduce((acc, item) => {
const files = Object.values(item.files);
if (files.length > 1) {
files.forEach((file) => acc.push(file.filename));
} else {
acc.push(files[0].filename);
}
return acc;
}, []);
console.log(myDesireResult);
})
.catch((e) => {
console.log("ERROR", e);
});
};
Explanation:
in the then block, can get the API call result with result.data
with reduce function, looping through the data will start.
since the object in the files has different names, we can get the files with Object.values() easily.
Some of the files contain several items and most of them have just one item. so with checking the length of the file we can do proper action. if the files have more than one element, with another simple lop, we can traverse this file array easily.
Check the working example on codesandbox

React.JS, how to edit the response of a first API call with data from a second API call?

I need to display some data in my component, unfortunately the first call to my API returns just part of the information I want to display, plus some IDs. I need another call on those IDs to retrieve other meaningful data. The first call is wrapped in a useEffect() React.js function:
useEffect(() => {
const getData = async () => {
try {
const { data } = await fetchContext.authAxios.get(
'/myapi/' + auth.authState.id
);
setData(data);
} catch (err) {
console.log(err);
}
};
getData();
}, [fetchContext]);
And returns an array of objects, each object representing an appointment for a given Employee, as follows:
[
{
"appointmentID": 1,
"employeeID": 1,
"customerID": 1,
"appointmentTime": "11:30",
"confirmed": true
},
... many more appointments
]
Now I would like to retrieve information about the customer as well, like name, telephone number etc. I tried setting up another method like getData() that would return the piece of information I needed as I looped through the various appointment to display them as rows of a table, but I learned the hard way that functions called in the render methods should not have any side-effects. What is the best approach to make another API call, replacing each "customerID" with an object that stores the ID of the customer + other data?
[Below the approach I've tried, returns an [Object Promise]]
const AppointmentElements = () => {
//Loop through each Appointment to create a single row
var output = Object.values(data).map((i) =>
<Appointment
key={i['appointmentID'].toString()}
employee={i["employeeID"]} //returned a [Object premise]
customer={getEmployeeData((i['doctorID']))} //return a [Object Promise]
time={index['appointmentTime']}
confirmed = {i['confirmed']}
/>
);
return output;
};
As you yourself mentioned functions called in the render methods should not have any side-effects, you shouldn't be calling the getEmployeeData function inside render.
What you can do is, inside the same useEffect and same getData where you are calling the first api, call the second api as well, nested within the first api call and put the complete data in a state variable. Then inside the render method, loop through this complete data instead of the data just from the first api.
Let me know if you need help in calling the second api in getData, I would help you with the code.
Update (added the code)
Your useEffect should look something like:
useEffect(() => {
const getData = async () => {
try {
const { data } = await fetchContext.authAxios.get('/myapi/' + auth.authState.id);
const updatedData = data.map(value => {
const { data } = await fetchContext.authAxios.get('/mySecondApi/?customerId=' + value.customerID);
// please make necessary changes to the api call
return {
...value, // de-structuring
customerID: data
// as you asked customer data should replace the customerID field
}
}
);
setData(updatedData); // this data would contain the other details of customer in it's customerID field, along with all other fields returned by your first api call
} catch (err) {
console.log(err);
}
};
getData();
}, [fetchContext]);
This is assuming that you have an api which accepts only one customer ID at a time.
If you have a better api which accepts a list of customer IDs, then the above code can be modified to:
useEffect(() => {
const getData = async () => {
try {
const { data } = await fetchContext.authAxios.get('/myapi/' + auth.authState.id);
const customerIdList = data.map(value => value.customerID);
// this fetches list of all customer details in one go
const customersDetails = (await fetchContext.authAxios.post('/mySecondApi/', {customerIdList})).data;
// please make necessary changes to the api call
const updatedData = data.map(value => {
// filtering the particular customer's detail and updating the data from first api call
const customerDetails = customersDetails.filter(c => c.customerID === value.customerID)[0];
return {
...value, // de-structuring
customerID: customerDetails
// as you asked customer data should replace the customerID field
}
}
);
setData(updatedData); // this data would contain the other details of customer in it's customerID field, along with all other fields returned by your first api call
} catch (err) {
console.log(err);
}
};
getData();
}, [fetchContext]);
This will reduce the number of network calls and generally preferred way, if your api supports this.

How to fetch correctly from server side in Node.js

I'd like to fetch/data and serverside fetch.
But I suffered following errors.
response.getCategory is not a function
(()=>{
const url = "/data";
fetch(url)
.then(response => {
console.log("getCategory_main.js",response.getCategory(1));
//displayQuiz(response,1);
});
})();
when we access /data, serverside fetch will be functioned.
const API_KEY="https://opentdb.com/api.php?amount=1&type=multiple";
const fetch = require('node-fetch');
const Quiz=require("../public/javascripts/quiz");
module.exports={
getQuiz:function(res){
fetch(API_KEY)
.then(response => response.json())
.then(json => { const quiz = new Quiz(json);
console.log("getCategory_model",quiz.getCategory(1));
console.log("quiz",quiz);
res.send(quiz);
});
}
};
I can get result
getCategory_model History
I should pass same data from serverside to clientside
but method access succeeded only in serverside..
What is the cause of this ? and how can I fix it ? thanks..
You can't send objects with live methods over the wire as JSON. That is, if your server side Quiz object has a getCategory() method, it won't have one when you send it over to the client.
You'll need to serialize it, e.g.
res.send({
quiz,
categories: [quiz.getCategory(1)],
});
When you fetch data, your'e fetching data (usually as text).
When you create a new quiz with new Quiz(json) your'e reading the json text data and using Quiz to create code from the json text.
So in your first example you should get the text result, and then evaluate the result to json so that you can use getCategory() from Quiz
const url = "/data";
fetch(url)
.then(data => data.json())
.then(json_text=> {
// here you use the text and parse to JSON
const data = JSON.parse(json_text)
// now you can create the Quiz object
const quiz = new Quiz(data)
console.log("getCategory_main.js", quiz.getCategory(1));
});

Using Node js, how to get weather feeds for multiple cities in single request using yahoo weather api

I'm using yahoo weather api for get weather feed of single city, now I want to get weather feed for multiple cities in single request, how can I do using yahoo api. I also want to know, is ther any api yahoo provides to get the list of city of any country.
My weather.js
import OAuth from 'oauth';
const header = {
"X-Yahoo-App-Id": "myappid"
};
const request = new OAuth.OAuth(
null,
null,
'myconsumerkey',
'myconsumersecret',
'1.0',
null,
'HMAC-SHA1',
null,
header
);
request.get('https://weather-ydn-yql.media.yahoo.com/forecastrss?w=713169&format=json', null,null, function (err, data, result) {
if (err) {
console.log(err);
} else {
console.log(data)
}
});
Using this code i can be able to get weather details for only one city i want to fetch weather details for multiple cities at once.
thanx in advance!!
So reading the documentation it doesn't seem possible to send a batch of locations to the Yahoo Weather API. But what you can do is .map() over an array of locations and make multiple requests.
https://developer.yahoo.com/weather/documentation.html#params
Since OAuth 1.0 is a callback, I've wrapped that with a new Promise(), which will give us an array of unfulfilled promises. Then finally, Promise.all() method returns a single Promise that fulfills when all of the promises passed as an iterable have been fulfilled.
const OAuth = require('oauth')
const header = {
'X-Yahoo-App-Id': 'your-app-id',
}
const request = new OAuth.OAuth(null, null, 'your-consumer-key', 'your-consumer-secret', '1.0', null, 'HMAC-SHA1', null, header)
const locations = ['pittsburgh,pa', 'london']
const getWeatherData = () =>
Promise.all(
locations.map(
location =>
new Promise((resolve, reject) =>
request.get(`https://weather-ydn-yql.media.yahoo.com/forecastrss?location=${location}&format=json`, null, null, (err, data) => {
if (err) return reject(err)
return resolve(data)
})
)
)
)
const main = async () => {
const test = await getWeatherData()
console.log(test)
}
main()
I have tested this with the API and here is an example response for the code above.
[
'{"location":{"city":"Pittsburgh","region":" PA","woeid":2473224,"country":"United States","lat":40.431301,"long":-79.980698,"timezone_id":"America/New_York"},"current_observation":{"wind":{"chill":32,"direction":280,"speed":5.59},"atmosphere":{"humidity":70,"visibility":10.0,"pressure":29.03,"rising":0},"astronomy":{"sunrise":"6:42 am","sunset":"7:59 pm"},"condition":{"text":"Partly Cloudy","code":30,"temperature":37},"pubDate":1586862000},"forecasts":[{"day":"Tue","date":1586836800,"low":38,"high":45,"text":"Mostly Cloudy","code":28},{"day":"Wed","date":1586923200,"low":32,"high":47,"text":"Partly Cloudy","code":30},{"day":"Thu","date":1587009600,"low":31,"high":45,"text":"Partly Cloudy","code":30},{"day":"Fri","date":1587096000,"low":35,"high":42,"text":"Rain And Snow","code":5},{"day":"Sat","date":1587182400,"low":35,"high":51,"text":"Scattered Showers","code":39},{"day":"Sun","date":1587268800,"low":42,"high":59,"text":"Rain","code":12},{"day":"Mon","date":1587355200,"low":43,"high":55,"text":"Mostly Cloudy","code":28},{"day":"Tue","date":1587441600,"low":37,"high":58,"text":"Partly Cloudy","code":30},{"day":"Wed","date":1587528000,"low":44,"high":61,"text":"Partly Cloudy","code":30},{"day":"Thu","date":1587614400,"low":50,"high":59,"text":"Mostly Cloudy","code":28}]}',
'{"location":{"city":"London","region":" England","woeid":44418,"country":"United Kingdom","lat":51.506401,"long":-0.12721,"timezone_id":"Europe/London"},"current_observation":{"wind":{"chill":46,"direction":70,"speed":6.84},"atmosphere":{"humidity":50,"visibility":10.0,"pressure":30.27,"rising":0},"astronomy":{"sunrise":"6:04 am","sunset":"7:58 pm"},"condition":{"text":"Mostly Sunny","code":34,"temperature":49},"pubDate":1586862000},"forecasts":[{"day":"Tue","date":1586818800,"low":38,"high":54,"text":"Partly Cloudy","code":30},{"day":"Wed","date":1586905200,"low":34,"high":62,"text":"Mostly Sunny","code":34},{"day":"Thu","date":1586991600,"low":38,"high":68,"text":"Partly Cloudy","code":30},{"day":"Fri","date":1587078000,"low":45,"high":62,"text":"Rain","code":12},{"day":"Sat","date":1587164400,"low":45,"high":60,"text":"Rain","code":12},{"day":"Sun","date":1587250800,"low":42,"high":63,"text":"Partly Cloudy","code":30},{"day":"Mon","date":1587337200,"low":44,"high":64,"text":"Scattered Showers","code":39},{"day":"Tue","date":1587423600,"low":44,"high":66,"text":"Partly Cloudy","code":30},{"day":"Wed","date":1587510000,"low":45,"high":67,"text":"Mostly Cloudy","code":28},{"day":"Thu","date":1587596400,"low":44,"high":65,"text":"Mostly Cloudy","code":28}]}',
]

How do I make multiple fetch calls without getting 429 error?

I came across a problem in a book which I can't seem to figure out. Unfortunately, I don't have a live link for it, so if anyone could help me with my approach to this theoretically, I'd really appreciate it.
The process:
I get from a fetch call an array of string codes (["abcde", "fghij", "klmno", "pqrst"]).
I want to make a call to a link with each string code.
example:
fetch('http://my-url/abcde').then(res => res.json()).then(res => res).catch(error => new Error(`Error: ${error}`)); // result: 12345
fetch('http://my-url/fghij').then(res => res.json()).then(res => res).catch(error => new Error(`Error: ${error}`)); // result: 67891
...etc
Each of the calls is going to give me a number code, as shown.
I need to get the highest number of the 5 and get its afferent string code and make another call with that.
"abcde" => 1234
"fghij" => 5314
"klmno" => 3465
"pqrst" => 7234 <--- winner
fetch('http://my-url/pqrst').then(res => res.json()).then(res => res).catch(error => new Error(`Error: ${error}`));
What I tried:
let codesArr = []; // array of string codes
let promiseArr = []; // array of fetch using each string code in `codesArr`, meant to be used in Promise.all()
let codesObj = {}; // object with string code and its afferent number code gotten from the Promise.all()
fetch('http://my-url/some-code')
.then(res => res.json())
.then(res => codesArr = res) // now `codesArr` is ["abcde", "fghij", "klmno", "pqrst"]
.catch(error => new Error(`Error: ${error}`);
for(let i = 0; i < codesArr.length; i++) {
promiseArr.push(
fetch(`http://my-url/${codesArr[i]}`)
.then(res => res.text())
.then(res => {
codesObj[codesArr[i]] = res;
// This is to get an object from which I can later get the highest number and its string code. Like this:
// codesObj = {
// "abcde": 12345,
// "fghij": 67891
// }
})
.catch(error => new Error(`Error: ${error}`));
// I am trying to make an array with fetch, so that I can use it later in Promise.all()
}
Promise.all(promiseArray) // I wanted this to go through all the fetches inside the `promiseArr` and return all of the results at once.
.then(res => {
for(let i = 0; i < res.length; i++) {
console.log(res[i]);
// this should output the code number for each call (`12345`, `67891`...etc)
// this is where I get lost
}
})
One of the problems with my approach so far seems to be that it makes too many requests and I get 429 error. I sometimes get the number codes all right, but not too often.
Like you already found out the 429 means that you send too many requests:
429 Too Many Requests
The user has sent too many requests in a given amount of time ("rate
limiting").
The response representations SHOULD include details explaining the
condition, and MAY include a Retry-After header indicating how long to
wait before making a new request.
For example:
HTTP/1.1 429 Too Many Requests
Content-Type: text/html
Retry-After: 3600
<html>
<head>
<title>Too Many Requests</title>
</head>
<body>
<h1>Too Many Requests</h1>
<p>I only allow 50 requests per hour to this Web site per
logged in user. Try again soon.</p>
</body>
</html>
Note that this specification does not define how the origin server
identifies the user, nor how it counts requests. For example, an
origin server that is limiting request rates can do so based upon
counts of requests on a per-resource basis, across the entire server,
or even among a set of servers. Likewise, it might identify the user
by its authentication credentials, or a stateful cookie.
Responses with the 429 status code MUST NOT be stored by a cache.
To handle this issue you should reduce the amount of requests made in a set amount of time. You should iterate your codes with a delay, spacing out the request by a few seconds. If not specified in the API documentation or the 429 response, you have to use trial and error approach to find a delay that works. In the example below I've spaced them out 2 seconds (2000 milliseconds).
The can be done by using the setTimeout() to execute some piece of code later, combine this with a Promise to create a sleep function. When iterating the initially returned array, make sure to await sleep(2000) to create a 2 second delay between each iteration.
An example could be:
const fetch = createFetchMock({
"/some-code": ["abcde", "fghij", "klmno", "pqrst"],
"/abcde": 12345,
"/fghij": 67891,
"/klmno": 23456,
"/pqrst": 78912,
});
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
(async function () {
try {
const url = "https://my-url/some-code";
console.log("fetching url", url);
const response = await fetch(url);
const codes = await response.json();
console.log("got", codes);
const codesObj = {};
for (const code of codes) {
await sleep(2000);
const url = `https://my-url/${code}`;
console.log("fetching url", url);
const response = await fetch(url);
const value = await response.json();
console.log("got", value);
codesObj[code] = value;
}
console.log("codesObj =", codesObj);
} catch (error) {
console.error(error);
}
})();
// fetch mocker factory
function createFetchMock(dataByPath = {}) {
const empty = new Blob([], {type: "text/plain"});
const status = {
ok: { status: 200, statusText: "OK" },
notFound: { status: 404, statusText: "Not Found" },
};
const blobByPath = Object.create(null);
for (const path in dataByPath) {
const json = JSON.stringify(dataByPath[path]);
blobByPath[path] = new Blob([json], { type: "application/json" });
}
return function (url) {
const path = new URL(url).pathname;
const response = (path in blobByPath)
? new Response(blobByPath[path], status.ok)
: new Response(empty, status.notFound);
return Promise.resolve(response);
};
}
In this case... You should run and wait each fetch run finish before run new fetch by using async/await
runFetch = async (codesArr) => {
for(let i = 0; i < codesArr.length; i++){
const rawResponse = await fetch(`http://my-url/${codesArr[i]}`);
const codeResponse = rawResponse.json();
console.log(rawResponse);
codesObj[codesArr[i]] = codeResponse;
}
}
hope that help you.

Categories

Resources