💁♀️ 비동기(Async) 작업이란,
메인 흐름은 멈추지 않는 상태에서 특정 작업들을 백그라운드에서 처리하여 동시에 처리하는 것처럼 실행
💁♀️ 동기(Sync) 작업이란,
하나의 작업을 실행하고 마친 뒤에 다음 작업을 순차적으로 실행
💁♀️ Promise란,
자바스크립트에서 비동기 처리를 위해 사용되는 객체로써 비동기 작업이 완료되었을 때 결과를 제공하거나 에러를 알리는 기능을 제공
- 콜백 지옥 같은 코드가 형성 되지 않게 하는 방안으로 ES6에서 도입
<body>
<script>
function increase(number) {
/* resolve는 성공, reject는 실패를 의미하는 매개변수 */
const promise = new Promise((resolve, reject)=> { // Promise객체에 매개변수와 함께 함수 전달
setTimeout(()=> {
const result = number + 10;
/* 내부에 50까지 반복하는 로직 추가 */
if(result > 50) {
const e = new Error('Number is TOO BIG💀');
/* 실패 시 결과 값을 reject로 전달 */
return reject(e);
}
/* 성공 시 결과 값을 resolve의 인자로 전달 */
resolve(result);
}, 1000
);
});
return promise;
}
console.log(increase(0));
/* promise에서 resolve 함수로 전달한 값은 then 메소드에 전달하는 콜백 함수의 매개변수를 이용해서 받기 가능
비동기 작업을 연달아서 수행한다고 해서 이전 예제처럼 콜백 지옥이 생기는 것이 아니라 promise 객체를 반환 받아
then 메소드를 통해 그 다음 작업을 설정하기 때문에 코드의 가독성이 개선됨 */
increase(0)
.then(number => { // then()을 활용하여 결과값을 꺼내옴 (number는 resolve가 보낸 result)
console.log(number);
return increase(number); // 처리 후 다시 promise를 리턴
})
.then(number => {
console.log(number);
return increase(number);
})
.then(number => {
console.log(number);
return increase(number);
})
.then(number => {
console.log(number);
return increase(number);
})
.then(number => {
console.log(number);
return increase(number);
})
/* Promise에서 설정한 reject가 리턴하는 e를 받아 발생시킨 에러를 잡아줌 */
.catch(e => { // 에러가 발생하는 것을 catch에서 잡음
console.log(e);
})
</script>
</body>
Promise를 더 쉽게 사용할 수 있도록 async
/await
가 ES2017(ES8)에서 추가
<body>
<script>
function increase(number) {
/* resolve는 성공, reject는 실패를 의미하는 매개변수 */
const promise = new Promise((resolve, reject)=> { // Promise객체에 매개변수와 함께 함수 전달
setTimeout(()=> {
const result = number + 10;
/* 내부에 50까지 반복하는 로직 추가 */
if(result > 50) {
const e = new Error('Number is TOO BIG💀');
/* 실패 시 결과 값을 reject로 전달 */
return reject(e);
}
/* 성공 시 결과 값을 resolve의 인자로 전달 */
resolve(result);
}, 1000
);
});
return promise;
}
/* 함수의 선언부에 async 키워드를 이용하고, 함수 내부에서 promise를 사용할 때 await 키워드를 이용
await 키워드가 붙은 promise 호출 구문은 promise 작업이 끝날 때까지 기다리며 실행하게 됨 */
async function run() {
try {
let result = await increase(0);
console.log(result);
result = await increase(result);
console.log(result);
result = await increase(result);
console.log(result);
result = await increase(result);
console.log(result);
result = await increase(result);
console.log(result);
result = await increase(result);
console.log(result);
} catch(e) {
console.log(e);
}
}
run();
</script>
</body>
🙋 fetch API 사용법
const promise = fetch(url, [options]);
- url : 접근하고자 하는 url
- options : 선택 매개변수로 method나 header등을 지정
options에 아무 값도 넣지 않으면 GET 메소드로 요청
<body>
<div id="root"></div>
<script type="text/babel">
async function callAPI() {
/* fetch API를 그냥 호출하면 promise를 반환 */
const promise = fetch('https://jsonplaceholder.typicode.com/users');
console.table(promise);
/* PromiseResult는 접근 불가 */
console.log(promise['[[PromiseResult]]']);
/* await를 이용하여 호출하면 응답 객체를 반환 */
const response = await fetch('https://jsonplaceholder.typicode.com/users');
console.table(response);
console.log(`본문 사용 여부 : ${response.bodyUsed}`) // false
/* response body를 문자열(text) 형태로 반환 */
// const responseText = await response.text();
// console.log(responseText); // 해당 url안의 문자열이 콘솔에 출력
/* response body를 자바스크립트 객체(json) 형태로 반환
한 번 본문을 사용하면 다시 사용할 수 없으므로 위의 코드는 주석처리 */
/* 위에서 사용한 text() 형태보다 보기 간결함 */
const json = await response.json();
console.log(json);
console.log(`본문 사용 여부 : ${response.bodyUsed}`) // true
}
function App() {
const onClickHandler = ()=> { callAPI(); };
return <button onClick={ onClickHandler }>API 요청</button>
}
ReactDOM.createRoot(document.getElementById("root")).render(<App />);
</script>
</body>
<body>
<div id="root"></div>
<script type="text/babel">
function callAPI() {
fetch('https://jsonplaceholder.typicode.com/users')
.then(response => response.json()) // fetch를 수행한 결과가 매개변수로 옴
.then(data => console.log(data));
}
function App() {
const onClickHandler = ()=> { callAPI(); };
return <button onClick={ onClickHandler }>API 요청</button>
}
ReactDOM.createRoot(document.getElementById("root")).render(<App />);
</script>
</body>
🙋 axios 라이브러리
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<body>
<div id="root"></div>
<script type="text/babel">
function callAPI() {
/* axios 라이브러리는 데이터 변환 처리를 내부적으로 해주기 때문에 조금 더 간편하게 사용 가능
=> 라이브러리 추가 필요 */
axios.get('https://jsonplaceholder.typicode.com/users') // get 방식으로 호출할 것
.then(res => console.log(res.data)) // fetch보다 더 간결
.catch(err => console.log(err)); // catch로 발생한 오류를 잡음
}
function App() {
const onClickHandler = ()=> { callAPI(); };
return <button onClick={ onClickHandler }>API 요청</button>
}
ReactDOM.createRoot(document.getElementById("root")).render(<App />);
</script>
</body>
<body>
<div id="root"></div>
<script type="text/babel">
const { useState } = React;
function callAPI({ word }) {
return axios.get('https://api.github.com/emojis') // get 방식으로 호출할 것
.then(res => {
console.table(res.data); // 확인용 출력
const image = res.data[word] // input박스에 입력된 word를 가져와서 해당 word와 맞는 이미지 url 전달
return image;
})
.catch(err => console.log(err));
}
function App() {
const [word, setWord] = useState('');
const [image, setImage] = useState('');
const onClickHandler = ()=> {
callAPI({ word }) // callAPI를 호출하면서 input에서 입력받은 값을 보내기
.then(image => setImage(image));
};
return (
<>
<input
type="text"
value={ word }
onChange={ (e)=> setWord(e.target.value) }
/>
<button onClick={ onClickHandler }>검색</button><br/>
{ image && <img src={ image }/>} {/* 이미지가 있다면 업로드 */}
</>
);
}
ReactDOM.createRoot(document.getElementById("root")).render(<App />);
</script>
</body>
<body>
<div id="root"></div>
<script type="text/babel">
const { useState } = React;
function SearchBox({ setImgUrl }) {
const [emojiName, setEmojiName] = useState('');
const onClickHandler = () => {
axios.get('https://api.github.com/emojis')
.then(res => {
setImgUrl(res.data[emojiName]);
});
}
return (
<>
<input type="search" name="emojiName" value={ emojiName } onChange={ e => setEmojiName(e.target.value) }/>
<button onClick={ onClickHandler } >검색</button>
</>
);
}
function ImageBox({ imgUrl }) {
return (
imgUrl && <img src={ imgUrl } width="200" height="200"/>
);
}
function App() {
const [imgUrl, setImgUrl] = useState('');
return (
<>
<SearchBox setImgUrl={ setImgUrl }/>
<br/>
<ImageBox imgUrl={ imgUrl }/>
</>
);
}
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
</script>
</body>
Cannot read properties of undefined
🤔❓
- 페이지를 로드 하자마자 데이터를 보여줘야 하는 상황
- useEffect()에서 호출하고 두 번째 인자로는 빈 배열을 전달. 그러면 최초 렌더링 시 데이터를 로드 가능
- 어떠한 동작에 의해서 데이터를 보여주고 싶다면 이벤트 핸들러에서 요청- state를 어디에서 관리해야 하는지에 대해서 고려하여 선언
App에서 관리하면 props로 전달할 수 있기는 하나, 수정할 경우 App 단위 전체로 렌더링 되므로 비효율적
<body>
<div id="root"></div>
<script type="text/babel">
const { useState, useEffect } = React;
function Title() {
return (
<h1>회원 목록</h1>
);
}
/* 여러 명의 정보를 가져와서 뿌려주는 역할의 컴포넌트 */
function ItemList() {
const [users, setUsers] = useState();
useEffect(
()=> {
fetch('https://jsonplaceholder.typicode.com/users') /* Promise에서 값을 꺼내려면 then() 사용 */
.then(response => response.json()) /* JSON 형태로 돌아오고 있기 때문에 json() 사용 */
.then(responseUser => setUsers(responseUser));
}, []
);
return (
/* users는 배열 */
<div>
{/* Item 컴포넌트에서는 한 명의 유저 정보를 관리, key는 받은 user데이터의 id로 설정 */}
{/* Cannot read properties of undefined 오류 발생 원인 :
실행 순서로 인해 useEffect는 최초 실행 시(mount) 호출이 되므로 users는 그 때 undefined임(비동기 통신이 되기 이전이므로).
따라서 users && 구문을 붙여 users가 있을 때만 map()이 실행되도록 함 */}
{ users && users.map(user => <Item user={ user } key={ user.id }/>)}
</div>
);
}
/* 한 명의 유저 정보를 화면에 노출시키는 컴포넌트 */
function Item({ user }) {
const onClickHandler = (id) => {
console.log(`클릭된 user의 ID 🤩 : ${ id }`)
}
return (
<div className="item" onClick={ ()=> onClickHandler(user.id) }>
<h4>{ user.name } ({user.username})</h4>
<p>
ID : { user.id } <br/>
이메일 : { user.email } <br/>
연락처 : { user.phone } <br/>
회사명 : { user.company.name } <br/>
</p>
</div>
);
}
function App() {
return (
<>
<Title/>
<ItemList/>
</>
);
}
ReactDOM.createRoot(document.getElementById("root")).render(<App />);
</script>
</body>
getCurrentPosition
<body>
<div id="root"></div>
<script type="text/babel">
const API_KEY = '/* hidden */';
const { useState, useEffect } = React;
function Weather() {
/* 빈 객체 및 문자열을 전달하여 Cannot read properties of undefined 오류 해결 */
const [position, setPosition] = useState({}); // 현재 위치의 위도&경도
const [cityname, setCityname] = useState('');
const [weather, setWeather] = useState({});
const [wind, setWind] = useState({});
useEffect(
()=> {
/* 위치 정보에 대한 조회가 완료된 이후, 날씨 정보 API로 요청이 되어야 하므로
두 가지 비동기 요청에 대한 순서를 반드시 정해서 수행해야만 함
=> Promise를 활용 */
new Promise((resolve, reject) => {
/* 1. 위도&경도 알아오기 */
/* getCurrentPosition(성공 시 핸들링 함수, 실패 시 핸들링 함수) => 비동기적으로 동작하는 함수 */
/* currentPosition : https에서만 작동 */
navigator.geolocation.getCurrentPosition(currentPosition => {
console.log(currentPosition);
setPosition({
latitude : currentPosition.coords.latitude,
longitude : currentPosition.coords.longitude
})
resolve(currentPosition.coords);
});
})
/* 비동기 이후 실행할 것을 then()에서 실행. then()에는 resolve()로 보낸 currentPosition.coords가 매개변수로 전달 */
.then(coords => {
/* 2. 날씨 API 요청하기 */
fetch(`https://api.openweathermap.org/data/2.5/weather?lat=${coords.latitude}&lon=${coords.longitude}&appid=${API_KEY}&lang=kr`)
.then(response => response.json())
.then(json => {
console.log(json);
setCityname(json.name);
setWeather(json.weather[0]);
setWind(json.wind);
})
});
}, []
)
return (
<>
<h1>현재 위치</h1>
<h4>{ `위도 : ${ position.longitude } 경도 : ${ position.latitude }` }</h4>
<h4>{ `조회한 도시 : ${ cityname }` }</h4>
<h4>{ `날씨 : ${ weather.main } 날씨 설명 : ${ weather.description }` }</h4>
<h4>{ `풍향 : ${ wind.deg } 풍속 : ${ wind.speed }m/s` }</h4>
</>
);
}
function App() {
return (
<>
<h1>🌞 오늘의 날씨 🌞</h1>
<Weather/>
</>
);
}
ReactDOM.createRoot(document.getElementById("root")).render(<App />);
</script>
</body>
<body>
<div id="root"></div>
<script type="text/babel">
const API_KEY = '91ee7041fd534cf9355e0a1b45f0486a';
const { useState, useEffect } = React;
function Weather() {
/* 빈 객체 및 문자열을 전달하여 Cannot read properties of undefined 오류 해결 */
const [position, setPosition] = useState({}); // 현재 위치의 위도&경도
const [cityname, setCityname] = useState('');
const [weather, setWeather] = useState({});
const [wind, setWind] = useState({});
/* 1. 위도&경도 알아오기 */
function getPosition() {
/* Promise 객체 반환 */
return new Promise((resolve, reject) => {
/* getCurrentPosition(성공 시 핸들링 함수, 실패 시 핸들링 함수) => 비동기적으로 동작하는 함수 */
/* currentPosition : https에서만 작동 */
navigator.geolocation.getCurrentPosition(currentPosition => {
console.log(currentPosition);
setPosition({
latitude : currentPosition.coords.latitude,
longitude : currentPosition.coords.longitude
})
resolve(currentPosition.coords);
});
});
}
/* 2. 날씨 API 요청하기 */
function getWeather(coords) {
return fetch(`https://api.openweathermap.org/data/2.5/weather?lat=${coords.latitude}&lon=${coords.longitude}&appid=${API_KEY}&lang=kr`)
.then(response => response.json());
}
/* 실행해야 할 함수 앞에 async를 붙이고 await로 실행 순서 지정 */
useEffect(
()=> {
const loadData = async ()=> {
/* 위치 정보에 대한 조회가 완료된 이후, 날씨 정보 API로 요청이 되어야 하므로
두 가지 비동기 요청에 대한 순서를 반드시 정해서 수행해야만 함
=> async/await를 활용 */
const coords = await getPosition();
const result = await getWeather(coords);
setCityname(result.name);
setWeather(result.weather[0]);
setWind(result.wind);
}
loadData();
}, []
)
return (
<>
<h1>현재 위치</h1>
<h4>{ `위도 : ${ position.longitude } 경도 : ${ position.latitude }` }</h4>
<h4>{ `조회한 도시 : ${ cityname }` }</h4>
<h4>{ `날씨 : ${ weather.main } 날씨 설명 : ${ weather.description }` }</h4>
<h4>{ `풍향 : ${ wind.deg } 풍속 : ${ wind.speed }m/s` }</h4>
</>
);
}
function App() {
return (
<>
<h1>🌞 오늘의 날씨 🌞</h1>
<Weather/>
</>
);
}
ReactDOM.createRoot(document.getElementById("root")).render(<App />);
</script>
</body>
<body>
<div id="root"></div>
<script type="text/babel">
const { useState, useEffect } = React;
function Title() {
return (
<h1>🌠🐭🐱 포켓몬 칭구들 🐹🐰🌠</h1>
);
}
function PokemonList() {
// 전체 포켓몬 목록에 대한 API 요청
// https://pokeapi.co/api/v2/pokemon/
const [pokemons, setPokemons] = useState();
useEffect(
()=> {
fetch('https://pokeapi.co/api/v2/pokemon/')
.then(res => res.json())
.then(resPokemon => {
console.log(resPokemon)
setPokemons(resPokemon.results) /* 응답 받은 것의 result가 배열임 */
});
}, []
);
return (
<div>
{ pokemons && pokemons.map(pokemon => <Card pokemon={ pokemon } key={ pokemon.url }/>) }
</div>
);
}
function Card({ pokemon }) {
const [image, setImage] = useState();
const [id, setId] = useState();
// 한 포켓몬의 이미지 주소 요청 => 이전 API 요청 결과의 url 속성으로 존재
// 결과에서 sprites.back_default 속성을 이용해 img 설정
useEffect(
()=> {
fetch(pokemon.url)
.then(res => res.json())
.then(resImage => {
setId(resImage.id)
setImage(resImage.sprites.back_default)
}
)
.catch(error => console.error(error));
}, []
);
return (
<div className="card" onClick={ ()=> alert(`I'm ${ pokemon.name } ^👅^`)}>
<div className="info">
<img src={ image }/>
<div>
<h2>No. { id }</h2>
<h3>name : { pokemon.name }</h3>
</div>
</div>
</div>
);
}
function App() {
return (
<>
<Title />
<PokemonList />
</>
);
}
ReactDOM.createRoot(document.getElementById("root")).render(<App />);
</script>
</body>
🤔❓ fetch 요청과의 차이
axios.get()
요청을 할 때 json형태로 바꾸는 과정이 필요하지않아 제외- 데이터의 경로가
fetch
요청을 했을 때와 약간의 차이 존재
(data를 상위에 가지고 있어 추가해줘야했음)
<body>
<div id="root"></div>
<script type="text/babel">
const { useState, useEffect } = React;
function Title() {
return (
<h1>🌠🐭🐱 포켓몬 칭구들 🐹🐰🌠</h1>
);
}
function PokemonList() {
// 전체 포켓몬 목록에 대한 API 요청
// https://pokeapi.co/api/v2/pokemon/
const [pokemons, setPokemons] = useState();
useEffect(
()=> {
axios.get('https://pokeapi.co/api/v2/pokemon/')
.then(resPokemon => {
console.log(resPokemon)
setPokemons(resPokemon.data.results) /* 응답 받은 것의 result가 배열임 */
});
}, []
);
return (
<div>
{ pokemons && pokemons.map(pokemon => <Card pokemon={ pokemon } key={ pokemon.url }/>) }
</div>
);
}
function Card({ pokemon }) {
const [image, setImage] = useState();
const [id, setId] = useState();
// 한 포켓몬의 이미지 주소 요청 => 이전 API 요청 결과의 url 속성으로 존재
// 결과에서 sprites.back_default 속성을 이용해 img 설정
useEffect(
()=> {
axios.get(pokemon.url)
.then(resImage => {
console.log(resImage)
setId(resImage.data.id)
setImage(resImage.data.sprites.back_default)
}
)
.catch(error => console.error(error));
}, []
);
return (
<div className="card" onClick={ ()=> alert(`I'm ${ pokemon.name } ^👅^`)}>
<div className="info">
<img src={ image }/>
<div>
<h2>No. { id }</h2>
<h3>name : { pokemon.name }</h3>
</div>
</div>
</div>
);
}
function App() {
return (
<>
<Title />
<PokemonList />
</>
);
}
ReactDOM.createRoot(document.getElementById("root")).render(<App />);
</script>
</body>
🤔❓ 나의 풀이와의 차이
image의 id를 가져온 것과는 달리 정규표현식을 사용하여 각 image의 주소값에서 id를 추출해옴
<body>
<div id="root"></div>
<script type="text/babel">
const { useState, useEffect } = React;
function Card({ pokemon }) {
// 한 포켓몬의 이미지 주소 요청 => 이전 api 요청의 결과에 url 속성으로 존재
// EX. https://pokeapi.co/api/v2/pokemon/1/
// 결과에서 sprites.back_default 속성을 이용해 img 설정
const [data, setData] = useState();
useEffect(
() => {
fetch(pokemon.url)
.then(response => response.json())
.then(json => setData(json));
},
[]
);
const regExp = /\/[0-9]{1,}/;
return (
<div className="card">
{ data && <img src={ data.sprites.back_default }/> }
<div className="info">
<h2>No. { pokemon.url.match(regExp).toString().replace('/', '') }</h2>
<h3>name : { pokemon.name }</h3>
</div>
</div>
);
}
function PokemonList() {
// 전체 포켓몬 목록에 대한 api 요청
// https://pokeapi.co/api/v2/pokemon
const [pokemons, setPokemons] = useState([]);
useEffect(
() => {
fetch('https://pokeapi.co/api/v2/pokemon')
.then(response => response.json())
.then(data => setPokemons(data.results));
},
[]
);
return (
<div>
{ pokemons.map(pokemon => <Card pokemon={ pokemon } key={ pokemon.name } />)}
</div>
);
}
function Title() {
return <h1>포켓몬 목록</h1>;
}
function App() {
return (
<>
<Title/>
<PokemonList/>
</>
);
}
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
</script>
</body>