환율·날씨 조회기
fetch 와 async/await 로 외부 데이터를 화면에 냅니다.
무엇을 만드나
도시를 고르면 지금 날씨를, 통화를 고르면 원화 환율을 보여 주는 화면을 만듭니다. 둘 다 실제 서버에서 받아 옵니다.
화면에 있을 것
- 도시를 고르는 자리와 조회 단추
- 결과를 보여 줄 칸
- 불러오는 중·실패 표시
지켜야 할 규칙
- 요청 중에는 단추를 잠급니다.
- 서버가 실패를 돌려주면 사람이 읽을 수 있는 문구로 알립니다.
- 끝나면 성공이든 실패든 단추를 다시 풉니다.
5부(fetch · async/await · 오류 처리) · 4부(DOM) · 3부(객체)를 사용합니다.
1단계 — 무엇이 오는지 먼저 봅니다
남의 서버가 어떤 모양으로 돌려주는지 모르면 코드를 쓸 수 없습니다. 먼저 받아서 그대로 살펴봅니다.
const url = "https://api.open-meteo.com/v1/forecast" + "?latitude=37.57&longitude=126.98" + "¤t=temperature_2m,relative_humidity_2m"; const response = await fetch(url); const data = await response.json(); console.log(response.ok); console.log(Object.keys(data.current)); console.log(typeof data.current.temperature_2m); console.log(data.current_units.temperature_2m);
온도 값 자체는 적어 두지 않았습니다. 실행할 때마다 달라지기 때문입니다. 대신 있는지·어떤 타입인지를 확인했습니다.
Object.keys 는 객체의 속성 이름을 배열로
돌려줍니다. 처음 보는 응답을 살필 때 편합니다.
2단계 — 필요한 것만 꺼냅니다
받은 것을 그대로 쓰지 말고 내 화면이 쓰는 모양으로 바꿉니다. 서버가 모양을 바꿔도 이 함수 하나만 고치면 됩니다.
const CITIES = { 서울: { lat: 37.57, lon: 126.98 }, 부산: { lat: 35.18, lon: 129.08 } }; async function loadWeather(city) { const { lat, lon } = CITIES[city]; const url = `https://api.open-meteo.com/v1/forecast` + `?latitude=${lat}&longitude=${lon}` + `¤t=temperature_2m,relative_humidity_2m`; const response = await fetch(url); if (!response.ok) throw new Error(`서버가 ${response.status} 를 돌려주었습니다`); const data = await response.json(); return { city: city, temperature: data.current.temperature_2m, humidity: data.current.relative_humidity_2m }; } const weather = await loadWeather("서울"); console.log(Object.keys(weather)); console.log(weather.city); console.log(typeof weather.temperature, typeof weather.humidity);
3.7 의 구조 분해로 좌표를 꺼냈고, ok 를 확인해
실패를 throw 로 올렸습니다(5.5).
3단계 — 환율도 같은 모양으로
async function loadRate(code) { const response = await fetch(`https://open.er-api.com/v6/latest/${code}`); if (!response.ok) throw new Error(`서버가 ${response.status} 를 돌려주었습니다`); const data = await response.json(); return { code: code, krw: data.rates.KRW, updated: data.time_last_update_utc }; } const rate = await loadRate("USD"); console.log(Object.keys(rate)); console.log(rate.code); console.log(typeof rate.krw); console.log(rate.krw > 0);
두 함수의 생김새가 같습니다 — 받아서, 확인하고, 내 모양으로 바꿔 돌려줍니다. 서버가 달라도 쓰는 쪽은 같게 다룰 수 있습니다.
4단계 — 세 상태를 화면에
const btn = document.querySelector("#load"); const box = document.querySelector("#box"); async function loadWeather() { const url = "https://api.open-meteo.com/v1/forecast" + "?latitude=37.57&longitude=126.98¤t=temperature_2m"; const response = await fetch(url); if (!response.ok) throw new Error("날씨를 받아 오지 못했습니다"); const data = await response.json(); return data.current.temperature_2m; } async function show() { btn.disabled = true; box.textContent = "불러오는 중..."; box.className = "loading"; try { const temperature = await loadWeather(); box.textContent = `서울 ${temperature}°C`; box.className = "done"; } catch (error) { box.textContent = `${error.message} 잠시 뒤에 다시 눌러 주세요.`; box.className = "error"; } finally { btn.disabled = false; } } btn.addEventListener("click", show); await show(); console.log(box.className); console.log(btn.disabled); console.log(box.textContent.endsWith("°C"));
온도는 바뀌므로 끝나는 글자만 확인했습니다. 미리보기 탭에서 실제 값을 볼 수 있고, 단추를 눌러 다시 받아 올 수도 있습니다.
모두 합치기
도시를 고르고 조회하면 날씨와 환율을 함께 받아 옵니다. 서로 기다릴 까닭이
없으므로 Promise.all 을 씁니다(5.3).
const CITIES = { 서울: { lat: 37.57, lon: 126.98 }, 부산: { lat: 35.18, lon: 129.08 } }; const select = document.querySelector("#city"); const btn = document.querySelector("#load"); const box = document.querySelector("#box"); async function loadWeather(city) { const { lat, lon } = CITIES[city]; const response = await fetch(`https://api.open-meteo.com/v1/forecast` + `?latitude=${lat}&longitude=${lon}` + `¤t=temperature_2m,relative_humidity_2m`); if (!response.ok) throw new Error("날씨"); const data = await response.json(); return { temperature: data.current.temperature_2m, humidity: data.current.relative_humidity_2m }; } async function loadRate() { const response = await fetch("https://open.er-api.com/v6/latest/USD"); if (!response.ok) throw new Error("환율"); const data = await response.json(); return Math.round(data.rates.KRW); } function draw(city, weather, krw) { box.replaceChildren(); const lines = [ `${city} ${weather.temperature}°C`, `습도 ${weather.humidity}%`, `1달러 = ${krw}원` ]; lines.forEach(line => { const div = document.createElement("div"); div.textContent = line; box.append(div); }); } async function show() { btn.disabled = true; box.textContent = "불러오는 중..."; box.className = "loading"; try { const city = select.value; const [weather, krw] = await Promise.all([loadWeather(city), loadRate()]); draw(city, weather, krw); box.className = "done"; } catch (error) { box.textContent = `${error.message} 정보를 받아 오지 못했습니다. 잠시 뒤에 다시 눌러 주세요.`; box.className = "error"; } finally { btn.disabled = false; } } btn.addEventListener("click", show); await show(); console.log(box.className); console.log(box.children.length);
Promise.all 은 하나라도 실패하면 통째로
실패합니다(5.6). 그래서 catch 하나로 둘을 함께
다룹니다. 실패한 쪽 이름을 오류 메시지에 담아 두었습니다.
미리보기 탭에서 도시를 바꿔 다시 조회해 보세요.
더 해 볼 것
도시를 셋 이상으로 늘리세요. 코드를 거의 고치지 않아도 됩니다.
CITIES 에 좌표를 더하고, 고르는 자리도 그 객체로 그리면 한 곳만 고치면 됩니다 — Object.keys(CITIES).forEach(...) 로 option 을 만듭니다(4.6).환율이 실패해도 날씨는 보여 주게 고치세요.
Promise.allSettled 는 실패해도 통째로 실패하지 않고 각각의 결과를 돌려줍니다(5.6). status 가 "fulfilled" 인 것만 그리면 됩니다.1분마다 저절로 다시 받아 오게 하고, 화면을 벗어나면 멈추게 하세요.
setInterval 로 되풀이하고 번호를 담아 둡니다. 멈출 때 clearInterval 을 부릅니다(5.1). 요청이 끝나기 전에 다음 것이 시작되지 않도록 지금 받는 중인지도 함께 봐야 합니다.이 프로젝트에서 배운 것
- 남의 서버를 쓰기 전에 무엇이 오는지 먼저 살핍니다.
- 받은 것을 그대로 쓰지 말고 내 화면이 쓰는 모양으로 바꿔 돌려줍니다.
- 불러오는 중 · 받았음 · 실패 세 상태를 모두 화면에 냅니다.
- 서로 상관없는 요청은
Promise.all로 함께 시작합니다. - 값이 계속 바뀌는 것은 고정된 출력으로 적지 않습니다.