728x90
📍 55일차 1.8.토. 온라인 강의
오늘은 JS 비동기, 동기, API 요청, callback, promise, async - await, OpenAPI, CORS 에 대해서 배웠다. 이전에 한번 배운내용이었고, 오늘 다시 복습한다는 마인드로 공부하니까 이해가 잘됐다.
❏ 자바스크립트 비동기
- 초기 웹 환경에서는 서버에서 모든 데이터를 로드하여 페이지를 빌드했으므로 JS에는 별도의 비동기 처리가 필요하지 않음
- Ajax 기술의 등장으로 페이지 로드 없이
client-side에서 서버로 요청을 보내 데이터를 처리할 수 있게 됨 XMLHttpRequest객체를 이용해 서버로 요청을 보낼 수 있게 됨JS는single-thread이므로, 서버 요청을 기다려야 한다면 유저는 멈춰있는 브라우저를 보게 된다. 따라서 동기가 아닌 비동기 처리를 이용해 서버로 통신해야 한다.- 비동기 요청 후,
main thread는 유저의 입력을 받거나, 페이지를 그리는 등의 작업을 처리 - 비동기 응답을 받으면, 응답을 처리하는
callback함수를task queue에 넣음 (동기적인 처리는 call stack) event loop는main thread에 여유가 있을 때task queue에서 함수를 꺼내 실행한다.- 브라우저에서 실행되는
JS코드는event driven시스템으로 작동한다. - 웹앱을 로드하면 브라우저는
HTML document를 읽어 문서에 있는CSS code.JS code를 불러옴 JS엔진(V8,blink)은 코드를 읽어 실행- 서버에서 바이트 스트링을보내면 브라우저에서 해당 스트링을 파싱하여 DOM tree 구조로 만든다.
css,js코드를 서버에 요청한 다음 바이트 스트림을 넘겨 받으면 다시 읽어서code를 만들어낸다. 이후에JS엔진이 해당 코드를 실행한다. - 브라우저의
main thread는JS코드에서 동기적인 코드외에도 웹 페이지를 실시간으로 렌더링하고, 유저의 입력을 감지하고, 네트워크 통신을 처리하는 등 수많은 일을 처리한다. - 비동기 작업을 할당하면 비동기 처리가 끝나고 브라우저는
task queue에 실행 코드를 넣는다. main thread는event loop를 돌려task queue에 작업이 있는지 확인하고, 동기적인 작업이 끝나면call stack으로 이동시킨 후task를 실행한다.
// async
request("user-data", (userData) => {
console.log("userData 코드");
saveUsers(userData)
}
consoel.log("DOM 변경")
consoel.log("유저 입력")
❏ Promise
- 비동기 처리 후 실행될 코드를
CB로 보내는 것 - 비동기 처리가 고도화 되면서,
CB hell등이 단점으로 부각 됨 Promise를 활용하여 비동기 처리의 순서 조작, 에러 핸들링, 여러 비동기 요청 처리 등을 쉽게 처리할 수 있게 됨
// callback pattern
setTimeout(() => {
console.log("Done");
}, 1000)
// callback pattern - single request
function fetchUsers(onSuccess){
return request('/users').then(onSuccess)
}
// Error handle 1
function fetchUsers(onSuccess, onError){
return request('/users')
.then(onSuccess)
.catch(onError)
}
// Error handle 2
return request('/users').then(onSuccess, onError)
// callback pattern - multiple request
function fetchUserAddress(onSuccess){
return request('/users', (userData) => {
const userDataWithAddress = [];
const userLength = userData.lenth;
userData.map(user => request(`/users/${user.userId}/address`, (address) => {
userDataWithAddress.push({ ...user, address })
if(userDataWithAddress.length === userLegnth){
onSuccess(userDataWithAddress)
}
})
})
}
// promise pattern - multiple request
function fetchUserAddress(){
return request("/users").then((userData) =>
Promise.all(
userData.map((user) =>
request(`/users/${user.userId}/address`).then((address) => ({
...user,
address
})
)
)
);
}
Promise객체는, 객체가 생성 당시 알려지지 않은 데이터에 대한Proxy- 비동기 실행이 완료된 이후
then,catch,finally등의 핸들러를 붙여 각각 데이터 처리 로직, 에러 처리로직, 클린업 로직을 실행한다. then체인을 붙여 비동기 실행을 마치 동기 실행처럼 동작하도록 한다.
function returnPromise(){
return new Promise((resolve, reject) => {
setTimeout(() => {
const randomNumber = generateRandomNumber(100);
if (randomNumber < 50) resolve(randomNumber)
else reject(new Error("Random number is too big."))
}, 1000)
})
}
// 비동기 코드를 동기스럽게 작성 할 수 있다.
returnPromise()
.then(num => {
console.log("first random number:", num)
})
.catch(error => {
console.error("Error occured", error)
})
.finally(() => {
console.log("Promise returned")
})
Promise객체는pending,fulfilled,rejected상태를 가짐fulfilled,rejected두 상태를settled라고 지칭pending은 비동기 실행이 끝나기를 기다리는 상태 이후promise핸들러를 붙여 조작가능fulfilled는 비동기 실행이 성공한 상태rejected는 비동기 실행이 실패한 상태then,catch는 비동기, 동기 실행 중 어떤 것이라도 리턴할 수 있음promise.all()은 모든 프로미스가fulfilled되길 기다림, 하나라도 에러 발생시 모든 프로미스 요청이 중단됨promise.allSettled()는 모든 프로미스가settled되길 기다림Promise.race()는 넘겨진 프로미스들 중 하나라도settled되길 기다림Promise.any()는 넘겨진 프로미스들 중 하나라도fulfilled되길 기다림
// Promise.all
Promise.all(
users.map((user) => request('/users/detail', user.name))
)
.then(console.log)
.catch(e => console.error("하나라도 실패했습니다."))
// Promise.allSettled
function saveLogRetry(logs, retryNum){
if(retryNum >= 3) return; // no more try
Promise.allSettled(logs.map(saveLog))
.then((results) => {
return results.filter((result) => result.status === "rejected");
})
.then((failedPromises) => {
saveLogRetry(
failedPromises.map((promise) => promise.reason.failedLog),
retryNum + 1
);
});
}
// Promise.race
function requestWithTimeout(request, timeout = 1000){
return Promise.race([request, wait(timeout)].then((data) => {
console.log("요청 성공");
return data;
}))
}
function wait(timeout){
return new Promise((resolve, reject) => {
setTimeout(() => {
reject()
}, timeout)
})
}
requestWithTimeout(req)
.then(data => console.log("data: ", data)
.catch(() => console.log("타임아웃 에러!"))
// Promise.any
function getAnyData(dataList){
Promise.any(dataList.map((data) => request(data.url)))
.then((data) => {
console.log("가장 첫 번째로 가져온 데이터 : ", data)
})
.catch((e) => {
console.log("아무것도 가져오지 못했습니다.")
});
}
promise chaining:Promise객체는settled되더라도 계속 핸들러를 붙일 수 있다. 핸들러를 붙인 순서대로 호출된다.catch뒤에 핸들러가 연속해서 붙어있다면, 에러를 처리한 후에 계속 진행된다. 이때는catch에서 리턴 값이 있다면then으로 전달된다.
728x90
❏ async / await
Promise체인을 구축하지 않고Promise를 직관적으로 사용할 수 있는 문법try - catch문으로 에러를 직관적으로 처리async function을 만들고,Promise를 기다려야 하는 표현 앞에await를 붙인다.then을 많이 붙이다보면 직관적이지 못할 수 있다.
// async/await
async function fetchUsers(){
try{
const users = await request('/users') // resolve data, then()
console.log("users fetched")
return users
}catch(e){ // catch()
console.log("error: ", e);
}
}
// Promise
function fetchUsers(){
return request('./users)
.then(users => console.log("users fetched"))
.catch(console.log("error: ", e));
}
- 여러 개의 await 을 순서대로 나열하여
thenchain을 구현할 수 있음
async function fetchUserAddress(id){
try{
const user = await request('/user/)
if(!user) throw new Error("No user found") // 조건 만족시 catch절로 이동
const address = await request(`/user/${user.id}/address`); // 위에서 작성한 user의 결과를 인자로 사용함
return { ...user, address }
}catch(e){ // promise의 모든 에러를 catch 할 수 있다.
console.log(e)
}
}
❏ async / await - Promise 조합
Promise.all은 특정 비동기 작업이 상대적으로 빨리 끝나도, 느린 처리를 끝까지 기다려야 하는데 이와 달리async/await를 사용할 경우parallelism을 구현할 수 있다. 즉, 빨리 끝난 대로 먼저 처리가 가능하다
async function fetchUserAddress(id){
return await Promise.all([
(async () => await request(`/users/${id}`))(),
(async () => await request(`/users/${id}/address`))(),
]);
}
fetchUserAddress("1234")
.then(([user, address]) => ({...user, address}))
.catch(e => console.log(e))
❏ POSTMAN, OpenAPI, CORS
- 서버와의 통신을 위해
API를 활용하는 경우,React앱으로만 요청하는 것은 비효율적이다. POSTMAN:API를 테스트하기 위한 개발 도구
❏ OpenAPI
RESTful API를 하나의 문서로 정의하기 위한 문서 표준OpenAPI Specification(OAS)로 정의됨Swagger등의 툴로,Open API로 작성된 문서를 파싱해 테스팅 도구로 만들 수 있음FE,BE협업 시 중요한 도구로 사용API의method,endpoint를 정의한다.endpoint마다 인증 방식,content type,body data,query string,path variable등 정의
❏ CORS
- 브라우저는 모든 요청 시
Origin헤더를 포함 - 서버는
Origin헤더를 보고, 해당 요청이 원하는 도메인에서 출발한 것인지를 판단 - 다른
Origin에서 온 요청은 서버에서 기본적으로 거부함. - 본격적인 요청을 보내기 전에
options에preflight를 통해 미리 서버에 요청을 보내본다. ( 서버option에서 처리 할 수 없다는CORS를 보내면 네트워크 요청을 실패하도록 한다. - 서버의
endpoint와 홈페이지의domain은 다른 경우가 많다. 따라서, 서버에서는 홈페이지domain을 이용하여 다른domain이라 하더라도 요청을 보낼 수 있게 한다. - 서버는
Accss-control-allow-origin외에Access-control-*을 포함하는 헤더에CORS관련 정보를 클라이언트로 보냄 - 브라우저의
same-origin-policy정책을 준수하기 위해 생겨난 개념 - 웹사이트에 악성
script가 로드되어, 수상한 요청을 하는것을 막기 위함 - 반대로 익명 유저로부터의
DDos공격 등을 막기 위함(여러가지 도메인을 만들어 특정 서버에 무차별적으로 요청을 보낸다. 그것을 브라우저가 1차적으로 방어해준다) - 서버에 직접
CORS요청을 할 수 없다면,Proxy서버 등을 만들어 해결한다.(proxy server: 클라이언트가 요청을 보낼 때CORS요청을 허용해준다. 요청을 보낼 때proxy서버는target.server에 요청을 보내고target.server는proxy에 요청정보를 응답한다. (proxy서버는 브라우저가 아니므로SOP,CORS을 사용하지 않는다.) 그리고proxy서버는CORS설정을 허용했기 때문에 다시 요청받은 브라우저로target Server에서 받은 내용을 뿌려준다.
반응형
'Frontend > 엘리스 SW 엔지니어 트랙' 카테고리의 다른 글
| [ 엘리스 SW 엔지니어 트랙 ] 57일차 (0) | 2022.01.12 |
|---|---|
| [ 엘리스 SW 엔지니어 트랙 ] 56일차(12주차: React III - 상태관리, Redux, 테스팅 기법, jest, react-testing-library ) (0) | 2022.01.11 |
| [ 엘리스 SW 엔지니어 트랙 ] 54일차 (0) | 2022.01.07 |
| [ 엘리스 SW 엔지니어 트랙 ] 53일차 (0) | 2022.01.06 |
| [ 엘리스 SW 엔지니어 트랙 ] 52일차 (0) | 2022.01.05 |
댓글