You could use native promises... It took me some experimenting to get used to the new javascript features like destructuring, template strings, .reduce
, and promises, but I like how the code looks...
You can return a Promise
from getPosition:
var getPosition = () => new Promise((resolve, reject) =>
navigator.geolocation.getCurrentPosition(resolve, reject))
$.getJSON
result can be treated like a promise. So getWeather
can take coords
and destructure into latitude
and longitude
right in the argument list, then using template strings you can just return $.getJSON()
var getWeather = ({ latitude, longitude }) =>
$.getJSON(`https://api.forecast.io/forecast/${FORECAST_API_KEY}/${latitude},${longitude}?callback=?`);
You can call getPosition()
and call Promise.all()
with promises to get the weather and location data at the same time. You can modify the value you return from .then()
or return another promise from it. So what I do here is convert the result of getWeather()
into an object with a weather
property with the result, same for location
, then use .reduce()
on Promise.all
waiting on those two promises, then Object.assign()
to merge all those results into one object like { position: {}, weather: {}, location: {} }
$(() => {
getPosition()
.then(position =>
Promise.all([
getWeather(position.coords).then(weather => ({ weather })),
getLocation(position.coords).then(location => ({ location }))
]).then(all => all.reduce((acc, val) => Object.assign(acc, val), { position }))
)
.then(data => {
console.log('data:', data);
displayWeather(data.weather, data.location);
})
.catch(err => alert('ERROR!\r\n' + JSON.stringify(err)))
})
So Promise.all()
ends up with an array that looks like this (...
is the data from the services):
[{weather:{...}, {location:{...}}]
Reduce starts with the accumulator (acc
) as {postion:{...}}
and operates on each element in that array calling Object.assign with the accumulator and the array value, returning the result with merged properties.
The Promise.all()
could be changed to use the array indices with the raw results and it looks cleaner, but I like to avoid accidentally using the wrong index for results. Also you can easily just add another ajax call without changing the then
...
Promise.all([
getWeather(position.coords),
getLocation(position.coords)
]).then(all => ({
weather: all[0],
location: all[1],
position
}))
I inserted the code as a snippet, but I can't seem to enable location running it. It works on jsfiddle though if you set the api keys...
let FORECAST_API_KEY = 'GET YOUR OWN';
// https://darksky.net/dev/account
let GOOGLE_API_KEY = 'GET YOUR OWN';
// https://developers.google.com/maps/documentation/geocoding/get-api-key
var displayWeather = function(weatherData, locationData) {
$('#userLocation').text(locationData.results[3].formatted_address);
$('#weatherDescription').text(weatherData.currently.summary);
$('#temperature').text(weatherData.currently.temperature);
}
var getPosition = () => new Promise((resolve, reject) => {
navigator.geolocation.getCurrentPosition(resolve, reject);
});
var getWeather = ({ latitude,longitude }) => {
return $.getJSON(`https://api.forecast.io/forecast/${FORECAST_API_KEY}/${latitude},${longitude}?callback=?`);
}
var getLocation = ({latitude, longitude }) => {
return $.getJSON(`https://maps.googleapis.com/maps/api/geocode/json?latlng=${latitude},${longitude}&key=${GOOGLE_API_KEY}`)
}
$(() => {
getPosition()
.then(position =>
Promise.all([
getWeather(position.coords).then(weather => ({ weather })),
getLocation(position.coords).then(location => ({ location }))
]).then(all => all.reduce((acc, val) => Object.assign(acc, val), { position }))
)
.then(data => {
console.log('data:', data);
displayWeather(data.weather, data.location);
})
.catch(err => alert('ERROR!\r\n' + JSON.stringify(err)))
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<p>
Replace <a href="https://darksky.net/dev/account">forecast.io</a> and
<a href="https://developers.google.com/maps/documentation/geocoding/get-api-key">google</a> api keys with your own!
</p>
<p><span id="userLocation">Loading...</span></p>
<p><span id="weatherDescription">Loading...</span></p>
<p><span id="temperature">Loading...</span></p>