비동기 Callback, Promise, async await

김민석·2022년 3월 13일
1

멘토링 프로그램

목록 보기
1/1
post-thumbnail

이 글은 코드스테이츠 멘토링 프그램의 멘토로서 멘티분들에게 도움을 주고자 작성된 글입니다.
멘티분들에게 부족한 정보를 전달하지 않기 위해 혹시 부족하거나 잘못된 정보가 있다면 댓글로 정정해주시면 감사드리겠습니다.
🙇‍♂️

Event Loop와 WebAPIs

개발자 도구 콘솔에서 아래의 코드를 실행시켜 보세요

console.log(window)

쭉~~~ 써있는 글씨들을 따라가다보면 setTimeout과 setInterval를 찾으실 수 있습니다.

setTimeout이라는 함수를 ‘선언’하지 않으셨음에도 setTimeout이라는 것을 사용하실 수 있던 것은

window라는 전역객체에 setTimeout이 이미 선언되어 있었기 때문이에요.

setTimeout이 브라우저에서 제공하는 Web API기 때문입니다.

즉, 브라우저쪽에서 제공되는 기능이라고 생각하시면 되겠어요.

브라우저는 그밖에 다양한 API 들을 제공합니다.(fetch도 있어요)

setTimeout은 다음과 같은 역할을 하죠.

  1. callback과 시간을 받는다.
  2. 받은 시간만큼 기다린다.
  3. 이후 callback을 실행
const sayHi = ()=>{
	console.log('hi')
}
setTimeout(sayHi,3000)

그럼 이 setTimeout은 어떻게 3초동안 기다려진 후에 실행시킬 수 있는 걸까요?

그것이 가능한 이유는 다음과 같습니다.

위 사진은 Event Loop라는 거에요.

조금 단순화된 형태이긴 합니다. :)

Event Loop는 JS가 돌아가는 환경에서 코드의 실행 타이밍을 관리하는 관리자입니다.

const sayHi = () =>{
	console.log('hi')
}

setTimeout(sayHi,3000)

console.log('bye')

위에서 보셨던 이 코드에서,

브라우저는 위에서부터 아래로 js를 읽으면서 순서대로 실행시키잖아요?

그런데 위 경우에는

const sayHi = () =>{
	console.log('hi')
}
setTimeout(sayHi,3000)
console.log('bye')
//1. bye
//2. hi

bye가 먼저 출력되고 hi가 먼저 출력됩니다. 그 이유는

해당 사진의 화질이 안좋긴 하지만 WebAPIs쪽에 setTimeout이 있는 게 보이시죠?

결론적으로 setTimeout은 브라우저가 실행합니다. 브라우저가 제공하고 있는 함수기 때문이죠.

자바스크립트는 위에서 부터 아래로 실행시키다가,

setTimeout을 만나면 브라우저에 실행을 위임하고 나머지 코드들을 쭉 읽는 거고요.

브라우저는 JS가 넘겨준 콜백을 갖고 3초동안 기다렸다가 3초가 끝나면 JS에게 콜백을 떤져주는 겁니다. 이제 실행시키라고 말이죠.

(이벤트 루프에 대해 공부하시면 이 부분이 이해가 더 잘 될거에요)

setTimeout이 그렇게 구현이 되어 있는 겁니다. 브라우저상에서요.




Callback에 의한 비동기 함수의 순서 보장

Promise와 async await은 결국 비동기 함수들의 실행 순서(혹은 타이밍)을 좀 더 명확하게 다루기 위한 도구들 입니다.

💡 실행 순서(타이밍) 라는 키워드가 중요합니다. 실행 순서요!

오늘 fetch에 대해서 배우셨죠?
앞서 말씀드렸던대로 fetch 또한 window에 구현되어 있는 api입니다.

fetch('url') // url에 request를 요청하고,
.then(res=>res.json()) // 그 다음 request를 json으로 parsing
.then(data => console.log(data) ) // 그 다음 콘솔에 출력

console.log('fetch completed!')

// 1. 'fetch completed!'
// 2. some data

브라우저 상에서 돌아갈 때 JS는 fetch를 만나면 브라우저에게 위임을 하겠죠.

만약, 브라우저 상에 있는 fetch라는 함수가 callback을 받는 함수였다면 어떨까요?

fetch라는 api는 존재하지 않고, fetchCallback이라는 api만 존재한다고 가정하겠습니다.

fetchCallback이라는 것은 다음과 같이 작동할 겁니다.

const fetchCallback = (url,callback) => {
	// 1. url을 향해 request를 날린다.
	// 2. response가 올때까지 기다린다.
	// 3. response가 오면 그 response를 넣어 callback을 실행한다
	// (response는 현재 함수내에 존재하지 않지만, 브라우저가 넣어준다는 '가정'을 하겠습니다.)
	
	// 즉, fetchCallback은 request이후 response가 오면
	// '⚠️그 다음⚠️'에 callback을 실행하는 함수입니다.
	callback(response)
}

그렇다면 fetchCallback 함수를 사용하려면 다음과 같이 코드를 작성해야겠죠.

fetchCallback('url', (response) => {
	
	// response의 method인 json 또한 비동기적으로 작동합니다.
	// 따라서 callback 함수로 실행순서를 보장하는 jsonCallback이란 함수를 가정했습니다.
	reponse.jsonCallback((data) => {
		console.log(data)
	})	
})

이런 식으로 작성해야 할 것입니다.

딱봐도 화가납니다. 👹

지금은 두개의 비동기만 다루기 때문에 괜찮지만 5개의 비동기 함수의 실행순서를 보장하려면

이런 것을 5번 해야합니다. 😅

따라서,

사람들은

fetchCallback을 좀 더 가독성 있게, 그리고 쉽게 다루고 싶어졌습니다.

그 방법으로 고안된게 Promise 객체입니다.

이제 Promise 객체를 이용해서 fetchCallback을 우리가 아는 fetch 함수와 똑같이 작동하게 만들겠습니다.

const fetch = ('url') => new Promise((resolve) => {
	// 앞서 같이 가정했던 것처럼 
	//fetchCallback은 response를 기다렸다가 
	//callback의 인자에 response를 넣고 실행시킵니다.
	fetchCallback('url', (response) => {
		resolve(response)
	})
})

fetch는 url이라는 parameter를 받아서 promise의 instance를 리턴하는 함수입니다.

이제 다음과 같이 작성할 수 있습니다.

fetch('url') // promise객체
.then(response => response.json) // json은 설명상 넘어갈게요! 😅
.then(data => console.log(data))

그럼 결국 Promise라는 것은 callback을 받아서 순서 보장을 받아야했던 함수들이

Promise 객체 덕분에

InstanceOfPromise
.then(callback1)
.then(callback2)
.then(callback3) //..... and more

한눈에 실행 순서를 보기 쉽게 작성할 수 있게 된 겁니다.

따라서, Promise는 비동기를 .then으로 실행순서를 이어가거나, .catch()로 오류를 핸들링하는 등의 작업을 쉽게 하기 위해 만들어진 객체입니다.




Async Await

async await은

.then(callback1).then(callback2).then(callback3) //..... and more

이런 식으로 사용하기보다는 원래 동기적으로 작동하는 함수들과 통일된 문법으로 비동기를 다루기 위해 고안된 문법입니다.

Promise를 기반으로 동작합니다.

즉, Promise는 비동기를 다루기 위한 객체였다면, async await은 promise를 다루기 위한 문법입니다.

우리의 친구 fetch를 다시 끌고와보겠습니다.

const fetchFromUrl = async (url) => {
		const response = await fetch(url) 
		const data = await response.json()
		console.log(data)
}

fetch라는 Promise를 await. 즉 기다리다가 Promise 내부에서 resolve 함수가 실행되면 response에 값을 할당하고 다음으로 넘어갑니다. (물론 실제로 그런건 아니고요 syntax sugar입니다.)

0개의 댓글