동기는 순차적, 직렬적으로 테스크를 수행하고, 비동기는 병렬적으로 테스크를 수행한다.
동기(Synchronous)
동기는 요청을 보낸 후 응답(결과물)을 받아야지만 다음 동작이 이루어지는 방식을 말한다.
모든 일은 순차적으로 실행되며 어떤 작업이 수행중이라면 다음 작업은 대기하게 된다.
function func1() {
console.log('첫번째 펑션!');
func2();
}
function func2(){
console.log('두번째 펑션!');
func3();
}
function func3() {
console.log('세번째 펑션');
}
func1();
// 출력값은 아래와 같다.
// 첫번째 펑션!
// 두번째 펑션!
// 세번째 펑션! 을 띄우게 된다.
비동기(Asynchronous)
비동기 처리는 왜 필요한가? - 데이터를 서버로부터 받아오는 앱을 만든다고 가정하면,
서버로부터 데이터를 받아와서 해당 데이터를 뿌려줘야 하므로 맨 처음에 서버로부터 데이터를 받아오는 코드가 실행되어야 할 것이다.
비동기로 처리하지 않고 동기적으로 구성을 하게 된다면 데이터를 받아오기까지 기다린 다음에 앱이 실행될 것이고 서버에 가져오는 데이터 양이 늘어날수록 앱의 실행속도는 기하급수적으로 느려진다.
데이터를 가져오기까지 앱이 대기하는 상태가 발생하게 된다. 이런 불편을 없애기 위해서
데이터를 수신하는 코드와 페이지를 표시하는 것과는 비동기적으로 처리를 해야한다.
그래서 비동기처리로 가장 많이 드는 예가바로 setTimeout과 AJAX가 있다.
function func1(){
console.log('func1');
func2();
}
function func2(){
setTimeout(function(){
console.log('func2');
}, 0);
func3();
}
function func3(){
console.log('func3');
}
func1();
/*
실행결과
func1
func3
func2
*/
자바스크립트는 싱글스레드로 프로그램이 동작한다. 비동기 처리방식은 필연적으로 다중스레드가 동작하는 멀티태스킹 작업일 수 밖에 없는데 어떻게 비동기가 가능한것인가? 라는 의문점을 가지게 된다.
자바스크립트는 웹 브라우저나 Node.js의 자바스크립트 엔진에서 실행된다. 이 엔진에는 자바스크립트를 돌리는 하나의 쓰레드가 존재한다. 또한 이 엔진 뿐만이 아니라 비동기식
처리 모델인 Web API라는 것이 함께 동작하면서 여기에서 setTimeout이나 AJAX로 http 데이터를 가져오는 시간이 소요되는 일들을 처리한다.
이 Web API들이 자바스크립트 엔진 스레드와는 다르게 비동기 처리를 따로 돌면서 Callback함수를 가지고 이벤트 loop에 들어가 처리되는대로 Callback함수를 다시 자바스크립트 엔진으로 돌려보내준다.
Callback
Callback함수는 특정 함수에 매개변수로 전달된 함수를 의미한다. 그 콜백함수는 함수를 전달받은 함수안에서 호출된다.
function Callback(callback){ console.log('콜백 함수'); callback(); } Callback(function(){ console.log('콜백 받는곳'); })
Callback함수에서 Callback을 받지 않는다면 함수의 과정이 끝나기도 전에 다음 프로세스를 진행하게 되는 경우가 있다.
Callback 함수는 가독성이 좋지않다.function Callback(callback){ function Callback2(callback){ function Callback3(callback){ console.log('무한콜백'); } } }
이것이 무한콜백이다. 가독성도 떨어지고 실수 위험도 커지게 된다. 그래서 ES7에서는 promise를 ES8에서는 async, await를 지원한다.
Promise
promise는 기본적으로 Callback이 하는일과 같다. 차이점은 promise는 작업이 끝난 후 실행할 함수를 제공하는 것이 아니라 promise자체 메소드인 .then()을 호출한다.
function add10(a) { return new Promise(resolve => setTimeout(() => resolve(a + 10), 100)); } //Promise사용 시 작업이 끝났음을 알려주는 resolve를 인자로 받아들임. add10(10) .then(add10) .then(add10) .then(add10) .then((res) => console.log(res))
Promise는 .then()과 같은 메소드를 연속적으로 사용이 가능한 이점을 가지고 있다. 따라서 callback을 사용했을 때와는 다르게 코드를 작성하고 이해하기가 한결 쉬워졌다.
Promise에서의 예외 처리
add10(10) .then((res) => { throw 'test error'; }) .catch((err) => console.log(err));
promise에서는 작업이 실패했을
경우 자동으로 .catch()메소드를 호출되게 한다.
기존 try-catch를 이용해서도 예외처리가 가능하지만 자바스크립트에서는 promise의 catch를 사용하라는 warning message를 출력함.
Async/Await
Node.js 7.6버전부터 구현된 기능이며
Async/Await
를 사용하면 promise에 비해 보다 쉽게 비동기적인 상황을 표현할 수 있다.async function f1() { const a = await add10(10); const b = await add10(a); console.log(a, b) } f1();
Async와 Await을 사용하려면 우선 사용할 함수 앞에 async라는 키워드를 붙여 사용해야 하며 선언된 async 함수 안에서만 await 키워드를 사용할 수 있다.
await은 함수의 작업이 끝나고 결과값을 반환할 때까지 대기하게 되며 결과 값이 리턴된다면 다음 작업으로 넘어가게 됩니다.
Async/Await의 예외 처리
async function f2() { const a = await add10(10).then(res => res); const b = await add10(a).catch(err => err); console.log(a, b) } f2();
위의 코드에선 add10()이 promise를 리턴하니까 promise가 지원하는 메소드를 사용이 가능하다. 그래서.catch()를 이용하여 예외처리를 할 수 있다.
async function f3() { try { const a = await add10(10) const b = await add10(a) console.log(a, b) } catch(err) { console.log(err) } } f3();
이렇게 기존방식인
try-catch
도 사용이 가능하다.