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

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}]}',
]

Related

Async/Await function failing

I'm trying to build a nodeJS script that pulls records from an Airtable base, bumps a UPC list up against the [UPC Item DB API][1], writes the product description ("Title") and product image array from the API response to an object, and then updates corresponding Airtable records with the pre-formatted using the Airtable API. I can't link directly to the Airtable API for my base, but the "Update Record" should look like this:
{
record_id: 'myRecord',
fields: {
'Product Description': 'J.R. Watkins Gel Hand Soap, Lemon, 11 oz',
'Reconstituted UPC': '818570001330',
Images: [
'https://images.thdstatic.com/productImages/b3e507dc-2d4a-48d4-a469-51a34c454959/svn/j-r-watkins-hand-soaps-23051-64_1000.jpg',
'http://pics.drugstore.com/prodimg/332476/450.jpg',
]
}
}
var Airtable = require('airtable');
var base = new Airtable({apiKey: 'myKey'}).base('myBase');
var request = require('request');
// Function to slow the code down for easier console watching
function sleep(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
// Function to slow the code down for easier console watching
async function init(x) {
console.log(1);
await sleep(x*1000);
console.log(2);
}
// Big nasty async function
async function imagesToAirtable() {
///Run through the airtable list
/// create the UPC_list that will be updated and pushed to Airtable to update records
const upc_list = [];
/// Pull from Airtable and assign array to an object
const airtable_records = await base('BRAND')
.select( { maxRecords : 3 })
.all()
/// Troubleshooting console.logs
console.log(airtable_records.length);
console.log("Entering the FOR loop")
/// Loop through the list, append req'd fields to the UPC object, and call the UPCItemDB API
for (var i = 0 ; i< airtable_records.length ; i++) {
/// Push req'd fields to the UPC object
await upc_list.push(
{ record_id : airtable_records[i].get("Record ID"),
fields: {
"Product Description" : "",
"Reconstituted UPC": airtable_records[i].get("Reconstituted UPC"),
"Images": []
}
}
);
/// Troubleshooting console.logs
console.log(upc_list)
console.log("Break");
/// call API
await request.post({
uri: 'https://api.upcitemdb.com/prod/trial/lookup',
headers: {
"Content-Type": "application/json",
"key_type": "3scale"
},
gzip: true,
body: "{ \"upc\": \""+airtable_records[i].get("Reconstituted UPC")+"\" }",
}, /// appending values to upc_list object
function (err, resp, body) {
console.log("This is loop "+ i)
upc_list[i].fields["Images"] = JSON.parse(body).items[0].images
upc_list[i].fields["Product Description"] = JSON.parse(body).items[0].title
console.log(upc_list[i]);
}
)}
};
imagesToAirtable();
I haven't gotten to writing the Airtable "Update Record" piece yet because I can't get the API response written to the upc_list array.
I get an error message on the last run of the FOR loop. In this case, the first and second time through the loop work fine and update the upc_list object, but the third time, I get this error:
upc_list[i].fields["Images"] = JSON.parse(body).items[0].images
^
TypeError: Cannot read property 'fields' of undefined
I know this has to do with async/await, but I'm just not experienced enough at this point to understand what I need to do.
I also know that this big nasty async/await function should be written into individual functions and then called in one single main() function but I can't figure out how to make everything chain together properly with async/await. Tips on that would be welcome as well :)
I have tried separating FOR loop into two FOR loops. The first for the initial append of the upc_list item, and the second for the API call and append with the parsed response.
I was going to skip by this question until I saw this:
I also know that this big nasty async/await function should be written
into individual functions
You are so right about that. Let's do it!
// get records from any base, up to limit
async function getRecords(base, limit) {
return base(base)
.select( { maxRecords : limit })
.all();
}
// return a new UPC object from an airtable brand record
// note - nothing async is being done here
function upcFromBrandRecord(brand) {
return {
record_id: brand.get("Record ID"),
fields: {
"Product Description": "",
"Reconstituted UPC": brand.get("Reconstituted UPC"),
"Images": []
}
};
}
The request module you're using doesn't use promises. There's a promise-using variant, I believe, but without installing anything new, we can "promise-ify" the post method you're using.
async function requestPost(uri, headers, body) {
return new Promise((resolve, reject) => {
request.post({ uri: uri, headers, gzip: true, body },
(err, resp, body) => {
err ? reject(err) : resolve(body)
}
)}
});
}
Now we can write a particular one for your usage...
async function upcLookup(brand) {
const uri = 'https://api.upcitemdb.com/prod/trial/lookup';
const headers = {
"Content-Type": "application/json",
// probably need an api key in here
"key_type": "3scale"
};
const body = JSON.stringify({ upc: brand.get("Reconstituted UPC") });
const responseBody = await requestPost(uri, headers, body);
// not sure if you must parse, but copying the OP
return JSON.parse(responseBody);
}
For a given brand record, build a complete upc record by creating the structure and calling the upc api...
async function brandToUPC(brand) {
const result = upcFromBrandRecord(brand);
const upcData = await upcLookup(brand);
result.fields["Images"] = upcData.items[0].images;
result.fields["Product Description"] = upcData.items[0].title;
return result;
}
Now we have all the tools needed to write the OP function simply...
// big and nasty no more!
async function imagesToAirtable() {
try {
const airtable_records = await getRecords('BRAND', 3);
const promises = airtable_records.map(brandToUPC);
const upc_list = await Promise.all(promises); //edit: forgot await
console.log(upc_list);
} catch (err) {
// handle error here
}
}
That's it. Caveat. I haven't parsed this code, and I know little or nothing about the services you're using, or whether there was a bug hidden underneath the one you've been encountering. So it seems unlikely that this will run out of the box. What I hope I've done is demonstrate the value of decomposition for making nastiness disappear.

Append a key/value pair to preexisting method that returns an object

I'm using the Heremaps API geocode method and I'm wondering if there is a way that I can append my own key/value pair that is an id of the record address that I'm running through the Heremaps API. The problem I'm running into is that the geocode method only takes an address parameter that it adds to the geocode endpoint call as its query string, but it doesn't accept any more arguments. I'm trying to come up with a way to call the geocode method with my address and append the record ID into the object that is returned so that each address that the API returns, has its original ID.
Since I can't change the geocode method because it's being called from a cdn https://js.api.here.com/v3/3.1/mapsjs-service.js, I need to append the record ID to the address that's being returned.
I added a coupleGeocodeRecordID function to attempt to couple the record ID with the object returned from geocode function call but it returns an error
Uncaught (in promise) TypeError: can't access property 1533, geoCoder(...) is undefined
app.js that makes the API call to the database to retrieve the record IDs (3), location names (6) and addresses (13) in the refSrcData const. The fetchAll call is just an API call to the database to pull these values.
const refSrcData = {
from: 'xxxxxxx',
select: [3, 6, 13],
};
const fetchAll = () => {
fetch('call to the endpoint', {
method: 'GET',
headers: {
'QB-Realm-Hostname': 'xxxxxxxxx',
Authorization: 'xxxxxxxxxxxxxxx',
'Content-Type': 'application/json',
},
body: JSON.stringify(refSrcData),
})
.then(resp => resp.json())
.then(data => {
getRefAddresses(data);
})
.catch(error => console.log('Error:', error));
};
// Step 1: initialize communication with the platform
const platform = new H.service.Platform({
apikey: 'xxxxxxxxxxxxxxxxxxxxx',
});
// Get an instance of the geocoding service:
const service = platform.getSearchService();
// Call the geocode method with the geocoding parameters,
// the callback and an error callback function (called if a
// communication error occurs):
const geoCoder = address => {
try {
service.geocode({
q: address,
},
result => {
// Add a marker for each location found
result.items.forEach(item => {
console.log(item);
});
}
);
} catch (err) {
console.log(err, `Can't reach the remote server`);
}
};
// Write a function that handles the record ID and couples with the geocode data
const coupleGeocodeRecordID = (address, recordId) => {
geoCoder(address)[recId] = recordId;
};
const getRefAddresses = async dataObject => {
const recordId = dataObject.data.map(e => e['3'].value);
const refAddress = dataObject.data.map(e => e['6'].value);
const refName = dataObject.data.map(e => e['13'].value);
for (let i = 0; i <= 10; i++) {
let location = refAddress[i];
location.length > 0
?
await coupleGeocodeRecordID(location, recordId[i]) :
false;
}
};
window.onload = () => {
fetchAll();
};
This is what is returned when I call the geocode function. I'm trying to attach the record ID to each object.
There are two problems:
Your geoCoder() function was not returning the result after the search. It was only logging it to the console. (You have to use promises with the geocode API, but promises work with async / await. I hope it makes sense)
Your coupleGeocodeRecordId() function is not doing anything with the results. I assume you plan to use them somehow. (Here, they are stored in an object called results for later use.)
I modified those two functions. The rest of your code looks fine, probably.
I can't test this code, so I don't know if there are other problems with how you're using the API.
const results = {};
const geoCoder = address => new Promise( finish => {
try {
//try to run the search.
service.geocode(
{ q: address, },
finish //on success, resolve the promise with the result
);
} catch ( err ) {
console.error( "Can't reach the remote server.", err );
finish( null ); //on failure, resolve the promise with null
}
} );
const coupleGeocodeRecordID = async ( address, recordId ) => {
//try to get a search result.
const result = await geoCoder( address );
if( result ) {
//attach the record id to the search result
result.recordId = recordId;
//store the result for use? I don't know what these are for
results[ address ] = result;
}
}

Retuning values from HTTP POST instead of Observable

I'm working on a city based angular application.
getPlaceId function will get the google place_id value.
Based on the place_id getPlacesPhotoRef should return 10 photo ref.
What I'm trying to do is, I wanted the photo ref to be pushed to photo's array.
expected output.
{
formatted_address: 'xxx',
place_id: 'xxx',
photos: [...] //All 10 photo ref
}
But issue is, instead of values, I see Observable getting returned in the photos array.
Below is my code
getPlaceId(cityName) {
let httpPath = `http://localhost:5001/calvincareemailservice/us-central1/webApi/api/v1/getplaces`;
return this.http.post(httpPath, { city: cityName }).subscribe(res => {
if (res) {
let data = JSON.parse(JSON.stringify(res));
this.placeIds.push({
formatted_address: data.candidates[0].formatted_address,
place_id: data.candidates[0].place_id,
photos: this.getPlacesPhotoRef(data.candidates[0].place_id)
.subscribe(res => {
let data = JSON.parse(JSON.stringify(res));
return data.result.photos.map(pic => pic.photo_reference);
})
}
);
}
});
}
getPlacesPhotoRef(id) {
let httpPath = `http://localhost:5001/calvincareemailservice/us-central1/webApi/api/v1/getplacesid`;
return this.http.post(httpPath, { placeId: id })
}
You are very close and thinking about the problem correctly, but the issue is you have assigned an Observable subscription to your photos key rather than the data the .subscribe() actually returned, which I would imagine is what you had hoped you were doing.
At a high level, what you want to do is push a new object to this.placeIds once you have all of the information it needs, e.g. formatted_address, place_id and photos. So what you need to do here is:
Call the /getplaces endpoint and store the place data
Call the /getplacesid endpoint using data.candidates[0].place_id and store the photos data
After both endpoints have returned construct an object using all the data and push this object to this.placeIds
Simple example with nested .subscribe() calls:
getPlaceId(cityName) {
const httpPath = `http://localhost:5001/calvincareemailservice/us-central1/webApi/api/v1/getplaces`;
return this.http.post(httpPath, { city: cityName })
.subscribe(res => {
if (res) {
const data = JSON.parse(JSON.stringify(res));
const formatted_address = data.candidates[0].formatted_address;
const place_id = data.candidates[0].place_id
this.getPlacesPhotoRef(place_id)
.subscribe(res => {
const data = JSON.parse(JSON.stringify(res));
const photos = data.result.photos.map(pic => pic.photo_reference)
this.placeIds.push({
formatted_address,
place_id,
photos
})
})
);
}
});
}
Note: A more elegant way to do this would be to use concatMap

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.

Fetch recursion using javascript to call Google place API

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

Categories

Resources