let a=10
console.log("a: ",a)
function foo(num){
for (let i=0;i<10;++i){
console.log(num)
}
}
-> 하던 일이 끝나야 다른 요청 처리 가능
let a=10
setTimeout(function callback(){
console.log('a: ',a)
},3000)
console.log('Finished')
-> 중간에 다른 요청 처리 가능
// 타이머 비동기 처리
setTimeout(() => console.log('타이머 끝'), 1000)
setInterval(() => console.log('인터벌 타이머'), 1000)
// 네트워크 처리
fetch('https://google.com')
.then(() => console.log('네트워크 요청 성공.'))
.catch(() => console.log('네트워크 요청 실패.'))
메인스레드: call stack(execution context stack)
비동기 환경: task queue, job queue
메인스레드 -> (비동기 코드 호출) -> 비동기 환경
메인스레드 <- (비동기 코드 처리, 결과 함수 반환) <- 비동기 환경
request("user-data",(userData) => {
console.log("userData 로드")
saveUsers(userData)
});
console.log("DOM 변경")
console.log("유저 입력")
동기적, 비동기적으로 카운터를 증가하는 Counter
const Counter = {
count: 0,
getCount: function () {
return this.count;
},
resetCount: function () {
this.count = 0;
},
incrementSync: function () {
// 동기적으로 3초 뒤에 this.count를 증가
// while 문 안에서, 또 다른 Date.now()를 구하여 3000을 초과하는 순간 while 문을 벗어남
const currentTime=Date.now()
while (true){
const now=Date.now()
if (now-currentTime>3000) break
}
this.count++
},
incrementAsync: function (callback) {
// 비동기적으로 3초 뒤에 this.count를 증가하며 callback을 호출
// setTimeout을 활용하세요.
setTimeout(()=>{
this.count++
callback()
},3000)
},
};
export default Counter;
setTimeout(()=>{
console.log("타임아웃1");
},0);
Promise.resolve().then(()=>console.log("프로미스1"));
setTimeout(()=>{
console.log("타임아웃2");
},0);
Promise.resolve().then(()=>console.log("프로미스2"));
// 프로미스1 프로미스2 -> 프로미스의 처리가 우선순위가 더 높다.
// 타임아웃1 타임아웃2
let promise=new
Promise((resolve,reject)=>
{
if(Math.random() < 0.5){
return reject("실패")
}
resolve(10)
})
----------------------------------------------------------------------------
- new Promise(callback)
- callback함수는 (resolve, reject) 두 인자를 받는다.
- Promise가 성공했을 때 resolve를 호출
- Promise가 실패했을 때 reject를 호출
promise
.then(data => {
console.log("성공:",data)
})
.catch(e => {
console.log("실패:",e)
})
.finally(() => {
console.log("promise 종료")
})
---------------------------------------------------------------------------
- then() 메서드에 성공했을 때 실행할 콜백 함수를 인자로 넘긴다.
- catch() 메서드에 실패했을 때 실행할 콜백 함수를 인자로 넘긴다.
- finally() 메서드는 성공/실페 여부와 상관없이 모두 실행할 콜백 함수를 인자로 넘긴다.
- then(callback1, callback2)로 callback1의 자리에 성송, callback2의 자리에 실패 메서드를 인자로 넘길 수 있다.
promise
.then(data => {
return fetchUser(data)
})
.then(user => {
console.log('User : ',user)
})
.catch(e => {
console.log("실패:",e)
})
----------------------------------------------------------------------------
- then/catch 메서드가 또 다른 promise를 리턴하여 비동기 코드에 순서를 부여한다.
- 이렇게 동일한 객체에 메서드를 연결할 수 있는 것을 체이닝이라 한다.
- 함수를 호출한 주체가 함수를 끝낸 뒤 자기 자신을 리턴하도록 구현
Promise
.resolve(10)
.then(console.log)
Promise
.reject("Error")
.catch(console.log)
----------------------------------------------------------------------------
- Promise.resolve 함수는 성공한 Promise를 바로 반환
- Promise.reject 함수는 실패한 Promise를 바로 반환
- 인위적으로 Promise 메서드 체인을 만들 수 있다.
- 비동기 코드로 진행해야 하는 상황 등에 유용하게 사용할 수 있다.
Promise.all([
promise1,
promise2,
promise3
])
.then(values => {
console.log("모두 성공: ",values)
})
.catch(e => {
console.log("하나라도 실패: ",e)
})
----------------------------------------------------------------------------
- Promise.all은 Promise의 배열을 받아 모두 성공 시 각 Promise의 resolved 값을 배열로 반환
- 하나의 Promise라도 실패할 시, 가장 먼저 실패한 Promise의 실패 이유를 반환
유저네임을 입력받아, 그 유저에 해당하는 주소를 검색하는 앱
UserInput 컴포넌트를 구현하여 앱. setValue는 유저로부터 받은 inputValue를 클로저의 value에 저장.
지시사항을 참고하여 searchAddress() 함수를 구현
import { findUserByUsername, findAddressByUserId } from "./api";
const UserInput = () => {
let value = "";
let error = "";
function getValue() {
return value;
}
function getError() {
return error;
}
function setValue(inputValue) {
value = inputValue;
}
// 지시사항을 참고하여 searchAddress() 함수를 구현
function searchAddress() {
error = "";
return findUserByUsername(value)
.then(user=>findAddressByUserId(user.id))
.catch(e=>{
error=e.message
})
}
return { getError, getValue, setValue, searchAddress };
};
export default UserInput;
async function asyncFunc(){
let data=await fetchData()
let user=await
fetchUser(data)
return user
}
----------------------------------------------------------------------------
- async함수는 function 키워드 앞에 async를 붙여 만든다.
- async 함수 내부에서 await 키워드를 사용한다.
- fetchData, fetchUser는 Promise를 리턴하는 함수
async function asyncFunc(){
let data1=await fetchData1()
let data2=await fetchData2(data1)
let data3=await fetchData3(data2)
return data3
}
function promiseFunc(){
return fetchData()
.then(fetchData2)
.then(fetchData3)
}
----------------------------------------------------------------------------
- await 키워드는 then 메서드 체인을 연결한 것처럼 순서대로 동작
- 비동기 코드에 쉽게 순서를 부여
function fetchData1(){
return request()
.then((response)=>
response.requestData)
.catch(error=>{
// error 발생
})
}
----------------------------------------------------------------------------
- Promise를 리턴하는 함수의 경우 에러가 발생하면 catch 메서드를 이용해 에러를 처리
- catch 메서드를 사용하지 않는다면 async 함수에서 try-catch 구문을 이용해 에러를 처리
async function asyncFunc(){
try{
let data1=await
fetchData1()
return fetchData2(data1)
} catch (e) {
console.log("실패:",e)
}
}
----------------------------------------------------------------------------
- try-catch 구문으로 async/await 형태 비동기 코드 에러 처리가 가능
- catch 절의 e는 Promise의 catch 메서드가 받는 반환값과 동일
요약
콜백 함수
비동기로 작동되는 함수
비동기 함수는 2초 뒤에 Elice라는 이름을 인자로 받은 콜백함수의 인자로 넘겨준다.function getName(cb) { setTimeout(() => { cb("Elice"); }, 2000); }
앞선 함수를 실행하려면 getName 함수에 콜백함수를 넣어서 사용 가능
getName((name) => { console.log(name); }) // 2초 후 Elice
getName 함수를 이용해서 Elice라는 이름을 3번 출력하려면?
getName 함수를 절차적으로 실행시키면 2초 뒤에 Elice라는 이름이 나온다.getName((name) => { console.log(name); }) getName((name) => { console.log(name); }) getName((name) => { console.log(name); }) // 2초 후 // Elice // Elice // Elice
하지만 앞선 방법으로 콜백함수를 호출하면 각 함수에 대한 데이터를 사용할 수 없다.
예를들어 이름, 나이, 주소가 저장된 데이터를 비동기적으로 가져와야 한다고 가정해보면function getName(cb) { setTimeout(() => { cb("Elice"); }, 2000); } function getAge(cb) { setTimeout(() => { cb(6); }, 2000); } function getAddress(cb) { setTimeout(() => { cb("Seoul"); }, 2000); }
해당 정보 출력하고자 한다. 하지만 console.log를 한 번만 사용해야한다.
getName((name) => { getAge((age) => { getAddress((address) => { console.log(name, age, address) }) }) })
앞선 코드처럼 콜백함수 안에 콜백 함수를 반복하여 호출해야 name, age, address를 한꺼번에 접근 가능
비동기 함수가 3개 쓰이고, 각 2초씩 걸리기 때문에 6초 뒤에 Elice 6 Seoul이라는 log가 나오게 된다.
바로 이것을 콜백 지옥이라 부른다. 비동기 함수를 3개만 썼을 뿐인데도,
코드가 정말 복잡해졌다. 비동기 함수가 더 많다면 관리하기 더 힘들다.Promise
Promise로 콜백 지옥을 보기 좋게 바꾸기!
앞서 사용한 비동기 함수인 getName, getAge, getAddress와 Promise 객체를 만들어서 사용해 같이 수정function getName() { return new Promise((resolve) => { setTimeout(() => { resolve("Elice"); }, 2000); }) } function getAge() { return new Promise((resolve) => { setTimeout(() => { resolve(6); }, 2000); }) } function getAddress() { return new Promise((resolve) => { setTimeout(() => { resolve("Seoul"); }, 2000); }) }
각 함수는 Promise 객체를 리턴. 그리고 Promise 객체는 항상 2초 후에 resolve 되어 이름, 나이, 주소에 대한 정보를 준다. 그리고 다음과 같이 호출하여 사용.
getName().then((res) => { console.log(res); }) getAge().then((res) => { console.log(res); }) getAddress().then((res) => { console.log(res); })
하지만 이렇게 사용하면 정보를 하나의 함수에서 제어하기 힘들다.
따라서 다음과 같이 사용할 수 있다.Promise .all([getName(), getAge(), getAddress()]) .then((res) => { const [name, age, address] = res; console.log(name, age, address) })
Promise.all은 첫번째 인자에 배열을 받는다. 그 배열의 원소는 모두 프로미스 객체
getName, getAge, getAddress 함수는 모두 프로미스 객체를 반환하기 때문에 Promise.all에서 사용 가능하다. 또한, Promise.all은 병렬적으로 배열의 원소에 있는 프로미스를 동시에 실행시킨다. 따라서 결과적으로 2초 후에 Elice 6 Seoul을 출력 가능하다. 즉, 동시에 Promise 객체를 반환하는 함수들을 실행가능 하다는 것. 콜백함수로는 할 수 없는 일이다.async/await
프로미스를 더 간단하게 사용하려면 다음과 같이 사용 가능하다.
즉시실행 함수 형태에 async 화살표 함수를 이용해 작성할 수 있다. await 키워드에서 프로미스가 resolve 될 때까지 기다린 후 다음 순서로 넘어가기 때문에 6초 후에 Elice 6 Seoul이 출력된다.(async () => { const name = await getName(); const age = await getAge(); const address = await getAddress(); console.log(name, age, address); })();
Promise와 async/await는 효과적으로 사용될 수 있는 상황이 다르기 때문에
두 방법 모두 잘 알고 있어야한다. 메소드 체이닝이 많이 사용되는 코드에서는 Promise가 코드에 일관성을 지켜서 더 깔끔하게 보일 수 있고, 개별 함수를 호출하여 값을 받아오는 경우에는 asyne/await이 효과적이다.
특정 시간 이후에 Promise를 resolve 하는 wait
함수
새로운 Promise를 리턴하는 wait
함수를 선언하세요.
wait
함수는 ms
단위로 기다리는 시간을 입력받습니다.wait(500)
- 500ms를 기다립니다.wait(1000)
- 1초(1000ms)를 기다립니다.wait
함수는 내부에 setTimeout
을 사용합니다.setTimeout
이후 wait 함수가 리턴하는 Promise는 resolve 됩니다.const wait = ms => new Promise(resolve=>setTimeout(resolve,ms))
클라이언트<-HTTP->Web<-HTTP->Server
let result = fetch(serverURL)
result
.then(response=>{
if(response.ok){
// 요청 성공
}
})
.catch(error=>{
// 요청 실패
})
----------------------------------------------------------------------------
- 기존 XMLHTTPRequest를 대체하는 HTTP 요청 API
- ES6에 추가된 Promise를 리턴하고록 정의
- 네트워크 요청 성공 시, Promise는 Response 객체를 resolve한다.
- 네트워크 요청 실패 시, Promise는 에러를 reject한다.
fetch(serverURL)
.then(response=>{
response.ok
response.status
response.statusText
response.url
response.bodyUsed
})
----------------------------------------------------------------------------
- Response 객체는 결과에 대한 다양한 정보를 담는다.
- response.ok는 HTTP Status code가 200-299 사이면 true, 그 외 false이다.
- response.status는 HTTP status code를 담는다.
- response.url은 요철한 URL 정보를 담는다.
fetch(serverURL)
.then(resp=>{
for(let [k,v] of
resp.headers){
console.log(k,v)
}
})
----------------------------------------------------------------------------
- response.headers로 Response 객체의 헤더 정보를 얻을 수 있다.
fetch(serverURL)
.then(response=>{
return response.json()
})
.then(json=>{
console.log('body : ', json)
})
----------------------------------------------------------------------------
- response.json() 메서드는 얻어온 body 정보를 json으로 만드는 Promise를 반환한다.
- Promise가 resolve 되면 얻어온 body 정보를 읽는다.
- response.text(), response.blob(), response.formData() 등의 메서드로 다른 형태의 바디를 읽는다.
fetch(serverURL,{
method:'post',
headers:{
'Content-Type':
'application/json;charset=utf-8',
Authentication:'mysecret'
},
body:JSON.stringify(formData)
})
.then(response=>{
return response.json()
})
.then(json=>{
console.log('POST 요청 결과:', json)
})
----------------------------------------------------------------------------
- fetch(url,options)로, fetch 메서드 옵션을 넣는다.
- method 필드로 여러 요청 메서드를 활용한다.
- headers,body 필드를 활용해 서버에 추가 정보를 보낸다.
https://randomuser.me
API를 이용해, 여러 유저의 정보를 받아와 가공하는 함수
API 정보를 가공하여 활용. User.js의 requestUsers 함수
1 유저 정보를 변환
- user.email -> email
- user.name.first, user.name.last -> name - `${user.name.first} ${user.name.last}`
- user.picture.large -> pictureUrl
- user.login.username -> username
- user.location.country, user.location.state, user.location.city -> location - `${user.location.country}, ${user.location.state}, ${user.location.city}`
- user.dob.age -> age
// 유저 정보가 다음과 같을 경우
{
gender: "female",
name: {
title: "Miss",
first: "Sara",
last: "Petersen",
},
location: {
street: {
number: 1383,
name: "Vægterparken",
},
city: "Sommersted",
state: "Nordjylland",
country: "Denmark",
postcode: 30790,
coordinates: {
latitude: "24.7161",
longitude: "11.7793",
},
timezone: {
offset: "-11:00",
description: "Midway Island, Samoa",
},
},
email: "sara.petersen@example.com",
dob: {
date: "1994-10-26T10:14:18.009Z",
age: 27,
},
registered: {
date: "2009-12-05T16:15:56.322Z",
age: 12,
},
id: {
name: "CPR",
value: "261094-1270",
},
picture: {
large: "https://randomuser.me/api/portraits/women/63.jpg",
medium: "https://randomuser.me/api/portraits/med/women/63.jpg",
thumbnail: "https://randomuser.me/api/portraits/thumb/women/63.jpg",
},
// ...
},
// 이렇게 변환됩니다.
{
email: 'sara.petersen@example.com',
name: 'Sara Petersen',
pictureUrl: 'https://randomuser.me/api/portraits/women/63.jpg',
username: 'happybear329',
location: 'Denmark, Nordjylland, Sommersted',
age: 27
}
age
가 40세 이상인 유저만을 필터링import API from "./api";
function transformUser(user){
const {first, last}=user.name
const {country,state,city}=user.location
const email=user.email
const name= `${first} ${last}`
const pictureUrl=user.picture.large
const username=user.login.username
const location=`${country}, ${state}, ${city}`
const age=user.dob.age
return {email,name,pictureUrl,username,location,age}
}
function filterByAge(user){
return user.age >= 40
}
const requestUsers = () => {
return API.fetchUsers().then((users) => {
// 유저 정보를 변화하고, 필터링하는 코드를 작성해보세요.
return users
.map(transformUser)
.filter(filterByAge)
});
};
export default requestUsers;
유저 정보, post 정보, 커멘트 정보를 응답하는 API를 활용, 하나의 포스트 목록
https://jsonplaceholder.typicode.com
의 API를 이용하여 데이터를 조합
Posts.js
의 requestPosts
메서드. requestPosts
는 posts 정보를 아래 지시사항에 따라 조합하여 리턴하는 비동기 함수
api.js
에 제공된 fetchPosts
, fetchUsers
, fetchComments
API를 이용하여 포스트 정보, 유저 정보, 커멘트 정보를 각각 요청하세요.fetchPosts
API를 이용하여 posts
목록을 가져옵니다.post
객체에는 userId
가 들어있습니다. userId
필드를 제거하고 userId
에 해당하는 유저 정보를 post
정보에 합칩니다. 유저 정보는 fetchUsers
에서 얻은 정보를 활용합니다.post
객체에는 id
가 들어있습니다. id
필드를 제거하고, 이 post id를 이용하여 comments
를 요청하고, 새롭게 comments 필드를 추가하여 응답받은 커멘트 목록을 post 합칩니다. 커멘트 정보는 fetchComments API를 활용합니다.fetchPosts
는 포스트 목록을 반환합니다.fetchUsers
는 유저 목록을 반환합니다.fetchComments
는 postId를 파라미터로 받아 해당하는 커멘트 목록을 반환합니다.posts
배열의 요소인 post
는 아래의 4개 필드를 가지고 있어야 채점이 정상적으로 이루어집니다.body, comments, title, user
----------------------------------------------------------------------------