자바스크립트 동기/비동기

Kyle·2022년 8월 4일
0

참고

JavaScript_동기&비동기(이벤트 루프)

자바스크립트 13. 비동기의 꽃 JavaScript async 와 await 그리고 유용한 Promise APIs | 프론트엔드 개발자 입문편 (JavaScript ES6)

JavaScript - async & await

자바스크립트 비동기 처리에 대하여 - 1

자바스크립트 중급 강좌 #17 async, await

Promise & async/await

동기/비동기란 ?

동기(synchronous)

  • 요청을 보낸 후 해당 응답을 받아야 다음 동작을 실행하는 것, 어떠한 코드를 실행했을때 해당 코드의 프로세스가 모두 완료 된 후, 그 뒤의 코드가 실행되는 것을 의미한다.

비동기(asynchronous)

  • 요청을 보낸 후 응답에 관계없이 다음 동작을 실행하는 것, 특정 코드가 실행되었을때 해당 코드의 프로세스가 어떻든간에 그 다음의 코드가 실행되는 것을 의미한다.

자바스크립트는 동기적이다.

자바스크립트를 비동기 언어라고 오해를 하는 경우도 있는데, 이는 우리가 Javascript를 비동기적으로 동작하도록 조작할 수 있기 때문이다.

비동기적으로 동작하는 경우는 WebAPI의 ajax, setTimeout(), Event handler, DOM 이 있다. 즉 브라우저엔진에서 작동할 때 비동기적으로 처리가 된다.

비동기로 동작하는 핵심요소는 자바스크립트 언어가 아니라 브라우저가 가지고 있다.

브라우저는 Web APIs, Event Table, Callback Queue, Event Loop 등으로 구성된다.

자바스크립트 코드가 실행될때 브라우저 동작은 아래그림을 통해 참고할 수 있다.

  • Heap: 메모리 할당이 발생하는 곳
  • Call Stack : 실행된 코드의 환경을 저장하는 자료구조, 함수 호출 시 Call Stack에 push 된다.
  • Web APIs: DOM, AJAX, setTimeout 등 브라우저가 제공하는 API
  • Callback Queue: 이벤트 발생 시 실행해야 할 callback 함수가 Callback Queue에 추가.
  • Event Loop
    • 1. Call Stack과 Callback Queue를 감시.
      1. Call Stack이 비어있을 경우, Callback queue에서 함수를 꺼내 Call Stack에 추가한다.

자바스크립트 브라우저 동작 순서

아래의 동작 순서를 이해하고나면, 자바스크립트 언어자체가 비동기 특성을 제공하는 것이 아니라, 브라우저의 구성 요소들이 제공한다는 것을 알 수 있다.

const cb = () => {
	console.log('second');
};

console.log('first');
setTimeout(() => cb(), 1000);
console.log('third');
  1. console.log(‘first’)가 Call Stack에 추가(push)

  2. console.log(‘first’)가 실행되어 화면에 출력한 뒤, Call Stack에서 제거(pop)

3.setTimeout(function cb() {..}) 이 Call Stack에 추가

  1. setTimeout 함수가 실행되면서 Browser가 제공하는 timer Web API 를 호출. 그 후 Call Stack에서 제거.

  2. console.log(‘third’)가 Call Stack에 추가

  3. console.log(‘third’)가 실행되어 화면에 출력되고 Call Stack에서 제거

  4. setTimeout 함수에 전달한 1000ms 시간이 지난뒤 Callback으로 전달한 cb 함수가 Callback Queue에 추가.

8. Event Loop는 Call Stack이 비어있는 것을 확인하고 Callback Queue를 살펴본다. cb를 발견한 Event Loop는 Call Stack에 cb를 추가

  1. cb 함수가 실행 되고 내부의 console.log(‘second’)가 Call Stack에 추가

  2. console.log(‘second’)가 화면에 출력되고 Call Stack에서 제거

  3. cb가 Call Stack에서 제거

이런식으로 자바스크립트는 브라우저엔진을 통해 비동기적으로 동작하게 된다.

개발을 진행하다보면 비동기적으로 동작하는 코드를 쓰게되는데 이를 동기적으로 실행하게 해주는 대표적인 방식으로는 Promise , async/await가 있다.

Promise

비동기 로직을 마치 동기처럼 사용할 수 있게 해주는 객체.

 const promise = new Promise((resolve, reject) => {
		// code
});

resolve : 성공했을때 실행되는 함수

reject : 실패했을때 실행되는 함수

callback 함수 : 어떤일이 완료되고 나서 실행되는 함수

new Promise 가 반환하는 객체

state : pending (대기)

result : undefined
  • resolve(value)가 호출되면
state : fulfilled (이행)

result : value // resolve 함수로 전달된 값
  • reject(error)가 호출되면
state : rejected (거부됨)

result : error // reject 함수로 전달된 error
  • 예제코드
const delivery = new Promise((resolve, reject) => {
	setTimeout(()=>{
	resolve('OK')
	}, 3000)
});

위 코드는 3초 후에 state: pending , result: undefined 상태에서 state :fufilled, result:'OK' 상태로 바뀐다.

const delivery = new Promise((resolve, reject) => {
	setTimeout(()=>{
	reject(new Error('error'))
	}, 3000)
});

위 코드는 3초 후에 state: pending , result: undefined 상태에서 state :rejected, result: error 상태로 바뀐다.

Promise 객체를 통해 value값을 얻기 위해서는 then 을 이용해주어야합니다.

const delivery = new Promise((resolve, reject) => {
	setTimeout(()=>{
	resolve('OK')
	}, 3000)
});

delivery.then(
	function(result){
		console.log(
			result + '의 주문이 완료되었습니다.'
		);
	}
).catch(
	function(err){
		console.log('다시 주문해주세요');
	}
)

콜백 지옥 ( Callback hell )

promise를 사용하면서 콜백지옥이라고하는 가독성이 안좋은 현상을 피할 수 있습니다.

아래 코드는 callback으로 구현한 동기 작업입니다.

const f1 = (callback) => {
	setTimeout(function () {
		console.log("1번 주문 완료");
		callback();
	}, 1000);
};

const f2 = (callback) => {
	setTimeout(function () {
		console.log("2번 주문 완료");
		callback();
	}, 3000);
};

const f3 = (callback) => {
	setTimeout(function () {
		console.log("3번 주문 완료");
		callback();
	}, 2000); 
};

console.log("시작");
f1(function() {
	f2(function() {
		f3(function() {
			console.log("끝");
		});
	});
});

Promise chaining

const f1 = () => {
	return new Promise((res, rej) => {
		setTimeout(() => {
			res("1번 주문 완료");
		}, 1000);
	});
};

const f2 = (message) => {
	console.log(message);
	return new Promise((res, rej) => {
		setTimeout(() => {
			res("2번 주문 완료");
		}, 3000);
	});
};

const f3 = (message) => {
	console.log(message);
	return new Promise((res, rej) => {
		setTimeout(() => {
			res("3번 주문 완료");
		}, 2000);
	});
};

console.log('시작');
f1()
.then((res) => f2(res))
.then((res) => f3(res))
.then((res) => console.log(res))
.catch(console.log) // 에러 처리
.finally(() => {
	console.log('끝');
});

Promise.all

위의 방법대로하면 총 6초라는 시간이 걸림, 이를 줄이기위해 한꺼번에 코드를 실행하고 결과를 도출하는 방법, promise에서 제공하는 API

Promise.all([f1(), f2(), f3()])
.then(res => {
	console.log(res);
});

// 세 작업이 모두 완료 후 then 이 실행된다.
> ["1번 주문 완료", "2번 주문 완료", "3번 주문 완료"]

Promise.race

  • 배열 내부의 코드 중 먼저끝나는 함수를 반환하고 종료된다.
Promise.race([f1(), f2(), f3()])
.then(res => {
	console.log(res);
});

// 1번 주문 함수가 가장 빨리 끝나기때문에, 1번 주문 후 Promise 종료되는것을 확인할 수 있음
> "1번 주문 완료"

async / await

Promise를 깔끔한 스타일로 더 쉽게 사용할 수 있도록 해주는 ES8 문법

async를 이용하면 promise의 then method를 이용하는 것 보다 가독성이 좋아진다.

await는 반드시 함수안에서 실행되어야 하며, await를 포함한 함수는 async 키워드를 추가해줘야한다.

async

  • 함수앞에 async를 선언해주면 따로 Promise 선언없이 Promise 로 바뀌는것을 확인 할 수 있다.
const something = async() => {
	return "Tech";
};

console.log(something());
> Promise {} // async 함수를 호출하면 promise 객체가 출력된다.

something().then((title) => {
	console.log(title);
});
> Tech

await

  • async 함수 내부에서만 사용할 수 있다. 그 외의 함수에서는 error 발생한다.
  • await는 Promise 객체를 반환하는 곳 or 기다릴 어떤 값 앞에 붙여줘서 사용할 수 있습니다.
const something = async (title) => {
	return new Promise((resolve, reject) => {
		setTimeout(() => {
			resolve(title);
		}, 1000);
	});
}

const getSomething = async () => {
	const result = await something("Tech");
	console.log(result);
}


console.log("시작");
getSomething();
> 시작
> Tech // 1초 후에 찍힘
  • Promise에서 처리해줬던 함수를 async/await로 표현하면 다음과 같다. 코드의 흐름과 순서가 명확하게 보이기때문에 가독성이 좋다.
const order = async() => {
	try { // 에러 핸들링
		const result1 = await f1();
		const result2 = await f2(result1);
		const result3 = await f3(result2);
		console.log(result3);
	} catch (e) {
		console.log(e);
	}
}

order()
> "1번 주문 완료"
> "2번 주문 완료"
> "3번 주문 완료"
// fruitPicker() 라는 Promise를 반환하는 함수가 있다고 가정

fruitPicker.create()
.then((category) => {
	fruitPicker.create(category.id)
	.then((fruit) => {
		fruit.send(fruitList);
	});
	fruitPicker.create(category.id)
	.then((fruit) => {
		fruit.send(fruitList);
	});
});

// async/await 적용
const category = fruitPicker.create();

const sendFruit = async (category) => {
	const fruit1 = await fruitPicker.create(category.id);
	await fruit1.send(fruitList);

	const fruit2 = await fruitPicker.create(category.id);
	await fruit2.send(fruitList);
}

async/await 병렬 처리

  • 각 promise를 병렬적으로 실행하여 처리해줄 수 있다.
  • Promise는 만드는 순간 Pormise 안의 코드블럭이 실행되기떄문에 병렬적으로 처리가 가능하다.
  • 이 방법은 각 코드들의 서로 병렬적으로 실행이 가능 한 경우에만 사용가능하다. 즉 getBody를 호출하는데 getTitle의 값이 필요없는경우에만 가능하다.
const delay = (time) => {
  setTimeout(
		()=>{}, time)
};

async function getTitle() {
	await delay(1000);
	return 'Title';
}

async function getBody() {
	await delay(1000);
	return 'Body';
}


async function getAll() {
	const titlePromise = getTitle();
	const bodyPromise = getBody();
	const title = await titlePromise;
	const body = await bodyPromise;
	return `${title} ${body} here`
}

getAll().then(console.log);
> 'Title Body' // 1초만에 실행되는것을 확인할 수 있다.

// 하지만 이방법으로 쓰게되면 코드가 길어지기때문에 위에서 다뤘던 Promise.all()을 사용하는것이 좋다.

react useEffect Hooks 에서 async-await 사용 예시

useEffect(() => {
	async () => {
		const getData = await axios.get("https://dailyfunding.com/users/1");
		setMemberInfo(getData.data);
	}
}, []);

사용자의 데이터를 얻어온 후 memberInfo 값을 셋팅해주기 위해서 API와 통신하는 부분에 await를 선언해 동기적으로 실행하게 만들어준다.

profile
깔끔하게 코딩하고싶어요

0개의 댓글