비동기 처리

primav·2024년 10월 7일

Javascript

목록 보기
14/14
post-thumbnail

이번 공부에서 도움을 받은 강의는 아래의 링크이다.

드림코딩 - 비동기 처리의 시작 콜백 이해하기
드림코딩 - 프로미스 개념부터 활용까지

콜백 함수

console.log('1');
setTimeout(function() {
 	console.log('2'); 
}, 1000); // 1초뒤에 함수를 call 해줘
console.log('3');

// 결과 1 3 2

간단하게만 말하자면 이 함수를 나중에 call 해줘 = callback 함수
특징은, 굳이 함수 선언문을 작성할 필요 없이 화살표 함수를 일반적으로 많이 사용한다.

setTimeout(() => {
 	console.log('2'); 
}, 1000); 

콜백 함수를 너무 많이 사용하면 콜백 지옥이 생겨난다.
콜백 지옥은 에러를 잡기도 힘들고, 코드의 가독성을 해친다.

Promise

자바스크립트에서 제공하는 비동기를 간편하게 처리할 수 있도록 도와주는 object
정해진 장시간의 기능을 수행하고 나서

  • 정상적으로 기능 수행 - 성공의 메세지 & 결과
  • 기능 수행 시 문제 발생 - 에러 전달

promise 에서 주의 깊게 볼 키워드는 두가지이다.

  1. state
  2. producer와 consumer 차이

state (상태)

🎯 프로세스가 무거운 작업을 실행하고 있는 중인지, 기능 실행이 성공 / 실패했는지

pending

지정한 작업이 수행중일 때

fulfilled

작업이 성공적으로 완료 되었을 때

rejected

파일을 찾을 수 없거나 / 네트워크에 문제가 생겼을 때

producer와 consumer 차이점

  • producer - 우리가 원하는 데이터를 제공하는 사람
  • consumer - 제공된 데이터를 사용하는 사람

🎯 정보를 제공하는 producer와 정보를 소비하는 consumer의 차이점을 알아보자

producer

원하는 기능을 수행해서 해당하는 데이터를 만들어내는 객체

// promise : 우리가 원하는 기능을 비동기적으로 실행하는 클래스 --> new 연산자로 객체 생성 가능
const promise = new Promise((resolve, reject => { // executor 콜백함수 (파라미터 : resolve, reject)
	
  	// heavy 한 작업 수행
  	// 큰 데이터를 읽어오는 과정은 시간이 걸림
  	// 오래 걸리는 작업을 동기적으로 수행하면 뒤의 작업을 수행하지 못함 
  	// 시간이 걸리는 일은 promise로 만들어서 비동기적으로 실행하는 것이 좋음
  	// ex) 네트워크, 파일 읽어오기 등
})
  • resolve - 기능을 정상적으로 실행해서 마지막에 최종 데이터를 전달 하는 콜백 함수
  • reject - 기능 수행 중 문제가 생기면 호출하는 콜백 함수

❗️ 새로운 promise가 만들어지면, executor 콜백 함수는 자동적으로 바로 실행된다.
따라서 만약 내가 원하는 순간에 작업을 수행하고 싶다면, 유의해야한다.

// promise : 우리가 원하는 기능을 비동기적으로 실행하는 클래스 --> new 연산자로 객체 생성 가능
const promise = new Promise((resolve, reject => { 
  	console.log('doing something');
  	setTimeout(() => {
      	resolve('ellie'); // resolve라는 콜백함수 : 성공적으로 데이터 전달 (네트워크를 받아온 / 파일에서 읽어온)
      	reejct(new Error('no network'));
    }, 2000);
})

consumer

우리가 원하는 데이터를 소비하는 객체
then, catch, finally 를 이용하여 값을 받아올 수 있다.

✔️ 값이 정상적으로 수행 될 때 (위의 producer - resolve 와 같이보기)

promise.then((value) => { // promise가 정상적으로 잘 수행 되어서 resolve 콜백함수를 통해 전달한 값이 value라는 파라미터로 전달
  	console.log(value);
})

✔️ 에러가 발생했을 때 (위의 producer - reject와 같이보기)

promise
  .then(value => { // then 호출 --> promise가 리턴
  	console.log(value);
	});
  .catch(error => { // 리턴된 promise의 catch 등록
  	console.log(error);
	});
// ex : arr.split('').join 처럼 

✔️ 성공 / 실패 여부와 상관없이 어떤 기능을 마지막으로 수행하고 싶을때

promise
  .then(value => {
  	console.log(value);
	});
  .catch(error => { 
  	console.log(error);
	});
  .finally(() => {
    console.log('finally') // 성공하든 실패하든 어떤 기능을 마지막으로 수행하고 싶을 때
  	});

프로미스 체이닝

then을 사용하여 여러가지를 동시에 묶어서 비동기적인 것들을 처리할 수 있다.

const fetchNumer = new Promise((resolve, reject) => {
	setTimeout(() => resolve(1), 1000);
});

fetchNumber
	.then(num => num * 2)
	.then(num => num * 3) 
	.then(num => { // 새로운 promise 클래스를 넣어도 됨
  		return new Promise((resolve, reject) => {
          	setTimeOut(() => resolve(num - 1), 1000);
        });
	})
	.then(num => console.log(num)); 

// 2초 뒤 5 출력

받아오는 값이 하나일 때

const getHen = () => {
	new Promise((resolve, reject) => {
      	setTimeout(() => resolve('egg'), 1000);
    });
}

getHen()
	.then(hen => getEgg(hen)) // 받아오는 값이 하나인 경우
	.then(cook) // 이 코드와 같이 생략 가능 --> 함수만 적어도 가능
	.then(console.log) // egg
	.catch(console.log) // error문도 하나만 받아오면 생략 가능
	

각 코드마다 에러문 설정

const getHen = () => {
	new Promise((resolve, reject) => {
      	setTimeout(() => resolve('egg'), 1000);
    });
}

getHen()
	.then(hen => getEgg(hen)) // 만약 에그에서 에러가 생기면 밑에가 전부 실행 안된다.
	// ⭐️ .catch('bread') ⭐️ // 이 코드를 넣어 에그의 에러가 생기면 빵을 집어 넣는다.
	.then(cook) 
	.then(console.log) // bread 로 바뀌어서 작동
	.catch(console.log) 
	

async / await

프로미스 체이닝을 계속 실행하면 코드가 난잡해진다. 이를 위해 더 간편한 API로 async / await 을 사용하면 동기적으로 코드를 작성하는 것처럼 코드를 쉽게 작성할 수 있다.

💡 synctactic sugar
기존에 존재하는 것 위에 더 간편하게 쓸 수 있는 API를 제공하는 것

  • 클래스
  • async / await

✔️ 프로미스 사용
"이 작업은 시간이 너무 오래걸리니까 then을 적고 뒤에 실행할 작업을 적어두면, 지금 오래 걸리는 작업을 끝낸 후 그 코드를 실행할 것을 약속할게"

function fetchUser() {
 	return new Promise((resolve, reject) => {
      	resolve('ellie');
    });
}

const user = fetchUser();
user.then(console.log);

console.log(user);

✔️ async
async 라는 키워드를 함수 앞에 붙이면 코드 블록이 자동으로 프로미스로 바뀜 ➡️ 프로미스를 사용 안해도 됨

async function fetchUser() {
 	return 'ellie';
}

const user = fetchUser();
user.then(console.log);

console.log(user);

✔️ await
async 키워드가 붙은 함수에서만 사용할 수 있다. 동기적으로 실행되는 코드와 비슷한 형식으로 작성할 수 있다.

async function getApple() {
 	await delay(3000);
  	return '🍎';
}

async function getBanana() {
 	await delay(3000);
  	return '🍌';
}

async function pickFruits() { // 일반 동기처리와 비슷하게 코드 작성 가능 --> 깔끔
 	const apple = await getApple();
  	const banana = await getBanana();
  	return `${apple} + ${banana}`;
}

pickFruits().then(console.log);

그러면 만약에 await을 사용하지 않았다면?
코드의 가독성이 떨어지고 콜백 지옥이 발생할 가능성이 생긴다.

✔️ await 사용 x

async function pickFruits() { 
 	return getApple()
  	.then(apple => {
      	return getBanana()
		.then(banana=> `${apple} + ${banana}`)
    })
}

async / await의 단점

  • 비동기를 동기적으로 처리하여 시간이 오래걸린다.
  • 연관이 없는 비동기 처리를 전부 기다려야 해서 오래걸린다.
    (ex: 사과 3초, 바나나 3초 총 6초가 넘게 걸림)

해결법 : Promise.all / Promise.race 사용

Promise.all

모든 프로미스들이 병렬적으로 실행되도록 한다.
모든 프로미스가 성공적으로 실행되면 then 작업을 실행한다.

function pickAllFruits() {
 	return Promise.all([getApple(), getBanana()]).then(fruits => 
      fruits.join(' + ')
    );
}

pickAllFruits().then(console.log);

Promise.race

어떤 것이든 상관없고 먼저 받아오는 하나의 프로미스를 반환한다.

function pickOnlyOne() {
 	return Promise.race([getApple(), getBanana()]) 
}

pickOnlyOne().then(console.log);

0개의 댓글