자바스크립트에서 비동기 처리 다루기

정혜윤·2021년 10월 19일
0

javascript

목록 보기
1/2

비동기 처리의 이해

동기적 vs 비동기적

동기적

작업이 끝나야 비로써 다른 작업 진행 가능

비동기적

동시에 여러가지 작업 가능
기다리는 과정에서 다른 함수 호출 가능

비동기 처리가 필요한 경우

  • Ajax Web API 요청
  • 파일 읽기
  • 암호화 / 복호화
  • 작업 예약

javascript 에서 비동기 처리가 필요한 이유

화면에서 서볼 데이터를 요청했을 때 서버가 언제 그 요청에 대한 응답을 줄지 모르는데 마냥 다른 코드를 실행 안 하고 기다릴 순 없기 때문.
이렇게 특정 로직의 실행이 끝날 때까지 기다려주지 않고 나머지 코드를 먼저 실행하는 것이 비동기 처리.
자바스크립트 비동기 처리 방식을 사용하는 방법은 바로 콜백(Callback) 함수를 이용하는 것.

콜백 지옥 (Callback hell)

콜백 지옥은 비동기 처리 로직을 위해 콜백 함수를 연속해서 사용할 때 발생하는 문제이다.
웹 서비스를 개발하다보면 서버에서 데이터를 받아와 화면에 표시, 사용자 인증 등을 처리해야 하는데 만약 이 모든 과정을 비동기로 처리해야 한다면 아래와 같이 콜백 안에 콜백을 계속 무는 형식으로 코딩을 하게 된다.
이러한 코드는 가독성도 떨어지고 로직을 변경하기도 어렵다.
이와 같은 구조를 콜백 지옥이라고 한다.

$.get('url', function(response) {
	parseValue(response, function(id) {
		auth(id, function(result) {
			display(result, function(text) {
				console.log(text);
			});
		});
	});
});

콜백 지옥 해결법

Promise 나 async 를 사용

Promise

ES6 문법으로써, 비동기 처리를 용이하기 위해 사용됨.

Promise는 javascript 비동기 처리에 사용되는 객체이다. 여기서 javascript 의 비동기 처리란 '특정 코드의 실행이 완료될 때까지 기다리지 않고 다음 코드를 먼저 수행하는 javascript의 특성' 을 의미한다.

function increaseAndPrint(n) {
	return new Promise((resolve, reject) => {
    	setTimeout(() => {
        	const value = n + 1; 
            if (value === 5) {
            	const error = new Error();
                error.name = 'ValueIsFiveError';
                reject(error)
                return;
            }
            console.log(value);
            resolve(value);
        }, 1000);
    });
}

increaseAndPrint(0).then(n => {
	return increaseAndPrint(n);
}).then(n => {
	return increaseAndPrint(n);
}).then(n => {
	return increaseAndPrint(n);
}).then(n => {
	return increaseAndPrint(n);
}).then(n => {
	return increaseAndPrint(n);
}).catch(e => {
	console.error(e);
})

Promise는 왜 필요한가?

Promise는 주로 서버에서 받아 온 데이터를 화면에 표시할 때 사용됨. 일반적으로 웹 애플리케이션을 구현할 때 서버에서 데이터를 요청하고 받아오기 위해 아래와 같은 API 를 사용함.

$.get('url 주소/products/1', function(response) {
  // ...
});

위와 같은 API가 실행되면 서버에다 '데이터 하나 보내주세요' 라는 요청을 함. 그런데 여기서 데이터를 받아오기 전에 마치 데이터를 다 받아온 것 마냥 화면에 데이터를 표시하려고 하면 오류가 발생하거나 빈화면이 뜨게 된다. 이와 같은 문제점을 해결하기 위한 방법 중 하나가 Promise 이다.

Promise의 3가지 상태 (states)

프로미스를 사용할 때 알아야 하는 가장 기본적인 개념이 바로 프로미스의 상태(states)다. 여기서 말하는 상태란 프로미스의 처리 과정을 의미한다. new Promise()로 프로미스를 생성하고 종료될 때까지 3가지 상태를 갖는다.

  • Pending(대기) : 비동기 처리 로직이 아직 완료되지 않은 상태
  • Fulfilled(이행) : 비동기 처리가 완료되어 프로미스가 결과 값을 반환해준 상태
  • Rejected(실패) : 비동기 처리가 실패하거나 오류가 발생한 상태

    프로미스 처리 흐름 - 출처 : MDN

Pending(대기)

먼저 아래와 같이 new Promise() 메서드를 호출하면 대기(Pending) 상태가 된다.

new Promise();

new Promise() 메서드를 호출할 때 콜백 함수를 선언할 수 있고, 콜백 함수의 인자는 resolve, reject 이다.

new Promise(function(resolve, reject) {
  // ...
});

Fulfilled(이행)

여기서 콜백 함수의 인자 resolve를 아래와 같이 실행하면 이행(Fulfilled) 상태가 된다.

new Promise(function(resolve, reject) {
  resolve();
});

그리고 이행 상태가 되면 아래와 같이 then()을 이용하여 처리 결과 값을 받을 수 있다. 즉 완료 상태가 될때 then() 에서 처리하면 된다.

function getData() {
  return new Promise(function(resolve, reject) {
    var data = 100;
    resolve(data);
  });
}

// resolve()의 결과 값 data를 resolvedData로 받음
getData().then(function(resolvedData) {
  console.log(resolvedData); // 100
});

Rejected(실패)

new Promise()로 프로미스 객체를 생성하면 콜백 함수 인자로 resolve와 reject를 사용할 수 있다. 여기서 reject를 아래와 같이 호출하면 실패(Rejected) 상태가 된다.

new Promise(function(resolve, reject) {
  reject();
});

그리고, 실패 상태가 되면 실패한 이유(실패 처리의 결과 값)를 catch()로 받을 수 있다. 실패시 로직을 여기서 처리하면 된다.

function getData() {
  return new Promise(function(resolve, reject) {
    reject(new Error("Request is failed"));
  });
}

// reject()의 결과 값 Error를 err에 받음
getData().then().catch(function(err) {
  console.log(err); // Error: Request is failed
});

Promise 의 에러 처리 방법

1.then()의 두 번째 인자로 에러를 처리하는 방법

getData().then(
  handleSuccess,
  handleError
);

2.catch()를 이용하는 방법

getData().then().catch();

but, Promise 에러 처리는 더 많은 예외 처리 상황을 위해 가급적 catch() 사용 권장

async, await

javascript 비동기 처리 패턴 중 가장 최근에 나온 ES8 문법
기존의 비동기 처리 방식인 콜백 함수와 Promise 의 단점을 보완하고 개발자가 읽기 좋은 코드를 작성할 수 있게 도와줌.
여기서 개발자에게 읽기 좋은 코드란 위에서 아래로 한줄한줄 차근히 읽으면서 사고하는 방식을 의미한다.

async, await 기본 문법

async function 함수명() {
  await 비동기_처리_메서드_명();
}

async, await 간단 예제

function sleep(ms) {
	return new Promise(resolve => setTimeout(resolve, ms));
}

async function makeError() {
	await sleep(1000);
    const error = new Error();
    throw error; 
}

// async await 예외 처리 
async function process() {
	try {
    	await makeError();
    } catch (e) {
    	console.error(e);
    }
}

process();

Promise.all, Promise.race

Promise.all

여러개의 promise를 실행하고 싶을 때 사용
but, 하나라도 에러가 날 경우 에러 반환.

function sleep(ms) {
	return new Promise(resolve => setTimeout(resolve, ms))
}

const getDog = async() => {
	await sleep(1000)
    return '강아지'
}

const getRabbit = async() => {
	await sleep(500)
    return '토끼'
}

const getTurtle = async() => {
	await sleep(3000)
    return '거북이'
}

async function process() {
	// 비구조화 할당 (구조분해) - 객체 안에 있는 값을 추출해서 변수 혹은 상수로 바로 선언 할 수 있음. 
	const [dog, rabbit, turtle] = await Promise.all([getDog(), getRabbit(), getTurtle()]); // promise.all 은 언제 끝나는가? 모든 promise 가 끝나면 각각 결과가 배열로 반환됨. 시간은 총 3000 걸림 
    
    // 각 promise 결과물 확인 
    console.log(dog)
    console.log(rabbit)
    console.log(turtle)
}

Promise.race

여러개의 promise 중 가장 빨리 끝나는 promise를 반환함.
첫번째 반환되는게 에러가 아니면 다른게 에러가 나더라도 에러 반환 X
첫번째 반환하는게 에러면 에러 반환

async function process() {
	const first = await Promise.all([getDog(), getRabbit(), getTurtle()]); 
    
    console.log(first) // 토끼 
}

참고

velopert javascript 강의

async function
https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Statements/async_function

자바스크립트 async와 await
https://joshua1988.github.io/web-development/javascript/js-async-await/

자바스크립트 비동기 처리와 콜백 함수
https://joshua1988.github.io/web-development/javascript/javascript-asynchronous-operation/

profile
frontend developer

0개의 댓글