eatthefrog

자바스크립트) 비동기함수 with 별코딩 본문

프론트엔드 노트

자바스크립트) 비동기함수 with 별코딩

eater_forg 2025. 7. 5. 20:06

목차

1. 동기 프로그래밍 VS 비동기 프로그래밍 

2. 비공기 콜백

3. Promise 완벽 정리

4. Promise.all + allSettled + any + race

5. Aync & Await

 

 

 

 

1. 동기 프로그래밍 VS 비동기 프로그래밍


동기 VS 비동기

  • 동기적 수행 :  순서대로 한번에 하나씩 (with 자바스크립트 싱글 스레드)
  • 비동기적 수행: 동시에 실행 (with Web APIs <- 브라우저:멀티 스레드)

 

Blocking

동기 프로그래밍에서 오래 걸리는 작업이 수행될때 뒤에 작업들이 밀리는 것


 

비동기 프로그래밍(non-blocking)

  • 동기 프로그래밍에서 발생하는 Blocking 문제를 해결할 수 있는 대안이다. 현재 실행 중인 테스크가 완료되지 않더라도, 다음 작업을 바로 실행할 수 있어 블로킹이 발생하지 않는(non-blocking)장점이 있다.
  • 그러나 작업이 언제 끝날지 예측할 수 없어서 작업의 실행 순서를 보장하기 어렵다는 단점도 존재한다. 그래서 이전 작업이 끝난 뒤에 실행되도록 보장하기 위해서, 콜백 안에 콜백을 넣는 방식을 사용하닥 콜백지옥을 만나게 된다.

 

 

 

 

2. 비공기 콜백


콜백 함수

  • 함수는 매개변수로 다양한 데이터를 받아올 수 있다. 이때 함수도 받을 수 있는데 이 함수를 콜백함수로 말한다. 즉, 콜백 함수는 다른 함수에 인자로 전달되는 함수다.
  • 콜백함수의 실행 조건은, 콜백함수를 매개변수로 받은 메인 함수에 구현사항에 따라서 결정된다. 그래서 이 콜백함수를 필요에 따라 즉시 실행할 수도 있고, 나중에 실행할 수도 있다.
  • 일반적으로도 아주 유용하지만, 비동기를 처리하는데도 많이 사용된다.

 

비동기 콜백(비동기 작업 완료 후 ,, 후처리)

비동기 콜백 예시


 

 

콜백 지옥 ( 콜백 기반 비동기 처리 방식)

 

순차적인 비동기 흐름 보장하기

  1. login -> 먼저 로그인 후
  2. addToCart -> 로그인한 사용자만 장바구니에 담을 수 있으므로, login 실행문 안쪽으로 들어감
  3. makePayment -> 장바구니에 담은 다음에야 결제를 진행할 수 있으므로, addToCart 실행문 안쪽으로 들어감

 

실행문 바깥에 쓰면?

: 비동기 함수는 setTimeout으로 나중에 실행도기 때문에, 아래처럼 작성하면 순서 보장이 안된다.

 

addToCart와 makePayment가 이전 함수 실행문 안으로 들어가 이유는?

: 이전 작업이 끝난 뒤에 실행되도록 보장하기 위해서, 콜백 안에 콜백을 넣는 방식 (콜백 지옥)을 사용한 것이다.

 


 

 

 

 

 

 

3. Promise 완벽 정리


Promise: 비동기 처리에 사용되는 자바스크립트 객체로 비동기 작업이 맞이할 성공 혹은 실패를 나타낸다.

 


콜백 지옥 형태의 코드를 Promise 버전으로 리팩토링하기

Promise 기반 함수로 변경하고, then() 체이닝으로 비동기 흐름 구현하기

  • 들여쓰기 깔끔해짐
  • 가독성 높아짐
  • 에러 처리를 .catch() 하나로 통합 가능

 

 


콜백 지옥 형태의 코드를 async/await 버전으로 리팩토링하기

  • 중첩 줄어듦(가독성 향상)
  • 흐름이 순차적으로 읽힘
  • 에러 헨들링이 명확함 (try/catch)

Promise 함수는 그대로 사용

 

 


new Promie 생성하기

  • const promise = new Promise((resolve,reject)=>{})
  • (resolve, reject)는 Promise 생성자 함수에 전달되는 콜백 함수의 매개변수이며, const promise는 이 콜백 함수의 결과로 생성된 Promise 객체이다.

✔️ (resolve, reject):

     → resolve: 작업이 성공했을 때 호출 (값을 전달할 수 있음)

     → reject: 작업이 실패했을 때 호출 (에러를 전달할 수 있음)

✔️const promise:

     → Promise 생성자에서 만들어진 "결과를 담는 객체" 

     → 나중에 .then(), .catch()로 처리 가능

 

 

 

.then(), .catch(), .finally()

 

프로미스 체이닝 (콜백 지옥)

  • 콜백 지옥은 단순한 비동기 흐름이지만 코드가 복잡해지고 관리가 어렵다.
  • Promise 체이닝은 가독성과 유지보수성 모두 뛰어나며, async/await로 확장도 쉬워 현재는 표준처럼 사용된다.

콜백 지옥

  • 각 작업이 이전 작업의 콜백안에 중첩
  • 예: login → addToCart → makePayment → reviewProduct → getCoupon

Promise

  • 각 작업을 .then()으로 순차적으로 연결
  • 에러는 .catch()로 한번에 처리 가능

 

 

Promise와 fetch API를 활용한 비동기 요청

 

 

 

fetch에서의 파싱

파싱: 문자열 형태의 데이터를 컴퓨터가 이해할 수 있는 구조로 분석하고 변환하는 과정이다.

fetch(url)
  .then((response) => response.json()) // 파싱하는 부분!

 

  • response.json()은 응답 내용을 JSON 형태로 파싱해서 자바스크립트 객체로 바꿔주는 함수다.
  • 이걸 해야 .then(data)에서 data.name, data.age 이런 식으로 접근할 수 있다.

 

 

4. Promise.all + allSettled + any + race

 

Promise.all() : 순차적 X(getName()끝나고, getTodo()를 실행), 병렬적으로 시행하며 const pormise에 배열 형태로 결과 값을 전달한다.Promise.all() 함수는 주어진 promise를 실행하다가 reject 되는 것이 하나라도 있으면 즉시 실행을 멈춥니다.

 

 

Promise.allSettled()

  • Promise.all() 함수는 주어진 promise를 실행하다가 reject 되는 것이 하나라도 있으면 즉시 실행을 멈춘다. 이때 실행이 빨린것의 결과를추출하기 때문에 어떤 비동기 작업에서 에러가 발생했는지 쉽게 알기 어렵다. 이때 사용할 수 있는것이 Promise.allSettled()이다.
  • Promise.allSettled() 메서드는 주어진 모든 프로미스를 이행하거나 거부한 후, 각 프로미스에 대한 결과를 나타내는 객체 배열을 반환한다. 일반적으로 서로의 성공 여부에 관련 없는 여러 비동기 작업을 수행해야 하거나, 항상 각 프로미스의 실행 결과를 알고 싶을 때 사용하다.
  • 그에 비해, Promise.all()이 반환한 프로미스는 서로 연관된 작업을 수행하거나, 하나라도 거부 당했을 때 즉시 거부하고 싶을 때 적합하다.

 

Promise.any()

가장 먼저 resolve가 된 promise의 값을 반환한다(한개). 특이한 점은 입력의 모든 프로미스가 reject되면 최종적으로 이 메서드도 거부 되며, 거부된 사유가 배열이 포함된 AggregateError('All prmise were rejected) 가 반환된다.

 

 

Promise.race()

전달해준 Promise들끼리 경주를 시켜서, resolve와 reject관계없이, 가장 빨리 완료된 Promise의 결과값을 반환한다.


5. Async & Await

async function : 함수 이름 앞에 async 키워드를 붙이면 항상 promise를 반환합니다. 만약 async 함수의 반환값이 명시적으로 promise가 아니라면 암묵적으로 promise로 감싸진다.

  • name: 함수의 이름
  • param: 함수에게 전달되기 위한 인자의 이름
  • statements: 함수 본문을 구성하는 내용. await 메커니즘이 사용될 수 있다.
    async function name([param[, param[, ... param]]]) {
        statements
    }

 

 

await: 후처리!

: 여러 promise의 동작을 동기스럽게(순차적으로) 사용할 수 있게 해준다. Promise chaining이 구조화된 callback과 유사한 것처럼 aync/await 또한 제네레이터(generator)와 프로미스(promise)를 묶는것과 유사하다. 

function networkRequest() {
	return new Promise((resolve) => {
    	setTimeout(() => {
        	console.log('데이터를 받아왔습니다.');
            resolve('서버1');
        }, 2000);
    });
}

// Case1: await X
// 결과: 유저, 데이터를 받아왔습니다.
async function getUser() {
  networkRequest(); // 비동기 요청 시작, 기다리지 않음
  return '유저';     // 바로 반환됨
}

// Case2: await O
// 결과: 데이터를 받아왔습니다, 유저
async function getUser() {
  await networkRequest(); // 2초 동안 기다림
  return '유저';
}

const user = getUser();
user.then((name) => console.log(name));

Note: await 키워드는 async 함수에서만 유효하다는 것을 기억하십시오. async 함수의 본문 외부에서 사용하면 SyntaxError가 발생합니다.

 

 

async/await VS Promise chaining 

: aync&await을 사용한 버전이 훨씬 가독성이 좋다.

// function networkRequest() {..} 생략

// async & await
async function getUser() {
  await networkRequest(); 
  await networkRequest();
  return '유저';
}

// promise chaining
function getUser() {
	return networkRequest()
    .then(()=>{
    	return networkRequest();
    })
    .then(()=>{
    	return "유저"
    })
}

const user = getUser();
user.then((name) => console.log(name));

 

 

async/await 활용하기:  비동기 작업을 처리하는 코드를,  순차적으로 보기 편하게 작성할 수 있다.

// function networkRequest() {..} 생략

async function getUser(){
	await networkRequest();
    return '유저';
}

async function getTodo() {
	await newtorkRequest();
    return ['공부하기','운동하기'];
}

async funtion getData() {
	const user = await getUser(); //2초 기다림
    const todo = auait getTodo(); //2초 기다림
    console.log(`${user}님 ${todo}를 하세요`); //총 4초 기다리고: '유저님 운동하고, 법먹기를 사에ㅛ
}

getData()

 

 

aync&await 에러 처리하기: try, catch

// function networkRequest() {..} 생략
// getUser에서 에러가 발생하도록 처리

async function getUser(){
	throw new Error('에러가 발생했어요!'); //✅에러가 발생하도록 처리함
	await networkRequest();
    return '유저';
}

async function getTodo() {
	await newtorkRequest();
    return ['공부하기','운동하기'];
}

async funtion getData() {
	let user; // 상태 변할 수 있으니까 const 말고 let에 담기
    try 
    	user = await getUser();
    } catch (error) {
    	console.log(error.message);
    }
    const todo = await getTodo(); //2초 기다림
    console.log(`${user}님 ${todo}를 하세요`); // 에러가 발생했어요!, (2초 후) undefined님 공부하고, 운동하기를 하세요.
}

getData()

 

 

 fetchAPI를 비동기적으로(async/await) 받아오기

fetch API

 

  • fetch()는 브라우저 내장 Web API이며, 서버에 네트워크 요청을 보낼 수 있게 해준다.
  • 호출하면 Promise 객체를 반환하므로, 비동기 처리에 사용된다.
  • 주로 서버에서 데이터를 받아올 때 사용된다. (예: REST API 호출).
    • 📦 fetch()는 기본적으로 GET 요청이며, POST, PUT, DELETE 등의 메서드는 options 객체를 추가해야 한다.

 

async/await과 함께 사용하는 이유

 

  • fetch() 자체가 Promise를 반환하므로, await를 사용해 응답이 도착할 때까지 기다릴 수 있다. (후처리!)
  • fetch()의 응답은 Response 객체이고, 이걸 다시 .json() 등의 메서드를 호출해서 실제 데이터를 추출(파싱)해야 한다.
  • response.json()도 비동기(Promise)이므로 await을 한 번 더 사용해야 실제 데이터를 얻을 수 있다.

 

async fucntion fetchData() {
	// 1. 서버에 요청 보내고 응답을 기다림
	const response = await fetch('url')  
        //2. 응답 내용을 JSON 데이터로 파싱함
    	const data = await response.json();
        // 3. 실제 데이터 출력
    	console.log(data);
}

fetchData();