초보 개발자의 성장 일기

비동기 통신과 Promise 본문

Development/React JS

비동기 통신과 Promise

YUNA 2023. 11. 24. 00:15

동기(synchronous)

해당 코드 블록을 실행할 때 thread의 제어권을 넘기지 않고 순서대로 실행하는 것을 의미한다.

console.log("This is synchronous...")

for (let i = 0; i < 1000000000; ++i) {
	console.log("I am blocking the main thread...")
}

console.log("This is synchronous...DONE!")

 

비동기(asynchronous)

코드의 순서와 다르게 실행된다. 비동기 처리 코드를 감싼 블록은 task queue에 넣어지며 main thread가 동기 코드를 실행한 후에 제어권이 돌아왔을 때 이벤트 루프task queue에 넣어진 비동기 코드를 실행한다.

setTimeout(() => console.log("This is asynchronous..."), 5000)

console.log("This is synchronous...")

for (let i = 0; i < 1000000000; ++i) {
	console.log("I am blocking the main thread...")
}

 

비동기 처리를 위한 내부 구조

  • 브라우저에서 실행되는 자바스크립트 코드는 event driven 시스템으로 작동한다.
  • 웹앱을 로드하면 브라우저는 HTML document를 읽어 문서에 있는 CSS code, JS code를 불러온다.
  • 자바스크립트 엔진은 코드를 읽어 실행한다.
  • 브라우저의 main thread는 자바스크립트 코드에서 동기적으로 처리되어야 할 코드 실행 외에도, 웹 페이지를 실시간으로 렌더링하고, 유저의 입력을 감지하고, 네트워크 통신을 처리하는 등 수많은 일을 처리한다.
  • 비동기 작업을 할당하면, 비동기 처리가 끝나고 브라우저는 task queue에 실행 코드를 넣는다.
  • main thread는 event loop를 돌려, task queue에 작업이 있는지 체크하고 작업이 있으면 task를 실행한다.

 

Promise

  • 비동기 처리의 순서 조작, 에러 핸들링, 여러 비동기 요청 처리 등을 쉽게 처리할 수 있게 됐다.
  • Promise 객체는, 객체가 생성 당시에는 알려지지 않은 데이터에 대한 Proxy이다.
  • 비동기 실행이 완료된 후에, `.then`, `.catch`, `.finally` 등의 핸들러를 붙여 각각 데이터 처리 로직, 에러 처리 로직, 클린업 로직을 실행한다.
  • then 체인을 붙여, 비동기 실행을 마치 동기 실행처럼 동작하도록 한다.
function fetchUsers(onSuccess) {
	return request('/users').then(onSuccess)
}

 

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 small."))
        }, 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 3개의 상태를 가진다.

fulfilled, rejected 두 상태를 settled 라고 지칭하고 pending은 비동기 실행이 끝나기를 기다리는 상태, fulfilled는 비동기 실행이 성공한 상태, rejected는 비동기 실행이 실패한 상태이다. then, catch는 비동기(Promise), 동기 실행 중 어떤 것이라도 리턴할 수 있다.

 

Multiple Promise handling

  • Promise.all()은 모든 프로미스가 fulfilled 되길 기다린다.
  • 하나라도 에러 발생시, 모든 프로미스 요청이 중단된다.
  • Promise.allSettled()는 모든 프로미스가 settled 되길 기다린다.
  • Promise.race()는, 넘겨진 프로미스들 중 하나라도 settled 되길 기다린다.
  • Promise.any() 넘겨진 프로미스 중 하나라도 fulfilled 되길 기다린다.
Promise.all(
users.map(user => request('/users/detail', user.name))
// [Promise, Promise, ... ,Promise ]
)
.then(console.log) // [UserNameData, UserNameData, ..., UserNameData]
.catch(e => console.error("하나라도 실패했습니다.")

 

Promise chaining, nested promise

  • Promise객체는, settled되더라도 계속 핸들러를 붙일 수 있다.
  • 핸들러를 붙인 순서대로 호출된다.
  • catch 뒤에 계속 핸들러가 붙어있다면, 에러를 처리한 후에 계속 진행되는데 이때는 catch에서 리턴한 값이 then으로 전달된다.
Promise.resolve()
    .then(() => wait2(500).then(() => console.log("500 waited.")))
    .then(() => {
        console.log("After 500 wait.")
        return wait2(1000).then(() => console.log("1000 waited."))
    })
    .then(() => console.log("DONE"))

/*
500 waited.
After 500 wait.
1000 waited.
DONE
*/

 

async/await

Promise 체인을 구축하지 않고도, Promise를 직관적으로 사용할 수 있는 문법이다. 많은 프로그래밍 언어에 있는 try ... catch 문으로 에러를 직관적으로 처리 한다. async function을 만들고, Promise를 기다려야 하는 표현 앞에 await을 붙인다.

async function fetchUsers() {
    try {
        const users = await request('/users')
        console.log("users fetched.")
    	return users
    } catch (e) {
    	console.log("error : ", e)
    }
}
  • 여러 개의 await을 순서대로 나열하여, then chain을 구현할 수 있다.
  • try... catch 문을 자유롭게 활용하여 에러 처리를 적용한다.
async function fetchUserWithAddress(id) {
    try {
        const user = await request(`/user/${user.id}`)
        const address = await request(`/user/${user.id}/address`)
        return { ...user, address }
    } catch (e) {
    	console.log("error : ", e)
    }
}
  • async/await을 활용할 경우 parallelism을 구현할 수 있다. 즉, 끝난 대로 먼저 처리될 수 있다.
async function fetchUserWithAddress(id) {
    return await Promise.all([
        (async () => await request(`/users/${id}`))(),
        (async () => await request(`/users/${id}/address`))(),
    ]);
}

fetchUserWithAddress('1234')
    .then(([user, address]) => ({ ...user, address }))
    .catch(e => console.log("Error : ", e))

 

POSTMAN

  • API를 테스트하기 위한 개발 도구이다.
  • Auth, header, payload, query 등 API 요청에 필요한 데이터를 쉽게 세팅한다.
  • 다른 개발자가 쉽게 셋업해 테스트할 수 있도록 API 정보를 공유할 수 있다.
  • Request를 모아 Collection으로 만들어, API를 종류별로 관리한다.
  • 환경 변수를 정의하여, 환경별로 테스트 가능하다.

 

Open API

  • RESTful API를 하나의 문서로 정의하기 위한 문서 표준이다.
  • OpenAPI Specification(OAS)으로 정의된다.
  • Swagger 등의 툴로, Open API로 작성된 문서를 파싱해 테스팅 도구로 만들 수 있다.
  • 프론트엔드 개발자, 백엔드 개발자와의 협업 시 주요한 도구로 사용한다.
  • API의 method, endpoint 를 정의한다.
  • endpoint마다 인증 방식, content type 등 정의한다.
  • body data, query string, path variable 등 정의한다.
  • 요청, 응답 데이터 형식과 타입 정의한다. data model 활용한다.(schema)

 

CORS

  • Cross-Origin Resource Sharing.
  • 브라우저는 모든 요청 시 Origin 헤더를 포함한다.
  • 서버는 Origin 헤더를 보고, 해당 요청이 원하는 도메인에서부터 출발한 것인지를 판단한다.
  • 다른 Origin에서 온 요청은 서버에서 기본적으로 거부한다.
  • 보통 서버의 endpoint와 홈페이지 domain은 다른 경우가 많다.
  • 서버에서는 홈페이지 domain을 허용하여, 다른 domain이라 하더라도 요청을 보낼 수 있게 한다.
  • 서버는 Access-Control-Allow-Origin 외에 Access-Control-* 을 포함하는 헤더에 CORS 관련 정보를 클라이언트로 보낸다.
  • 웹사이트에 악성 script가 로드되어, 수상한 요청을 하는 것을 막기 위함이다.
  • 익명 유저로부터의 DDos공격 등을 막기 위함이다.
  • 서버에 직접 CORS 설정을 할 수 없다면, Proxy 서버 등을 만들어 해결한다.

#엘리스트랙 #엘리스트랙후기 #리액트네이티브강좌 #온라인코딩부트캠프 #온라인코딩학원 #프론트엔드학원 #개발자국비지원 #개발자부트캠프 #국비지원부트캠프 #프론트엔드국비지원 #React #Styledcomponent #React Router Dom #Redux #Typescript #Javascript

'Development > React JS' 카테고리의 다른 글

Redux  (0) 2023.11.29
리액트 상태관리  (0) 2023.11.27
리액트와 라우터의 관계  (0) 2023.11.22
리액트 앱에서의 스타일링 방법  (1) 2023.11.20
리액트 기초 (2) - Hooks  (0) 2023.11.17