동기(Synchronous)와 비동기(Asynchronous)
- JavaScript
📖 Reference 📎 https://tlsdnjs12.tistory.com/15 📎 https://ko.javascript.info/async-await
🤔 JS에서의 동기/비동기 처리란?

동기와 비동기를 나누는 가장 큰 차이점은 어떤 실행 순서를 가지는 지에 있다.
- 동기적(Synchronous) : 요청을 보낸 후 응답을 받아와야 다음 동작을 실행
- 먼저 시작된 하나의 작업이 끝날 때까지 다른 작업을 시작하지 않고 기다렸다가 다 끝나면 새로운 작업을 시작한다.
- 비동기적(Asynchronous) : 요청을 보낸 후 응답과 관계 없이 다음 동작을 실행
- 먼저 시작된 작업의 완료 여부와는 상관없이 새로운 작업을 시작한다.
→ 프로세스가 위 사진과 같이 병렬로 배치되어 실행되기에 작업의 순서가 확실하지 않는다.
→ 심지어 나중에 시작된 작업이 먼저 끝나는 경우도 발생한다.
💡 자바스크립트 비동기처리
자바스크립트는 기본적으로 비동기적(Asynchronous) 으로 동작한다.
JS에서도 아래와 같은 경우에 비동기적 처리가 발생한다.
👉 DOM Element의 이벤트 핸들러 → 마우스, 키보드 입력 등, 페이지 로딩(DOMContentLoaded 등)
👉 타이머 → setTimeout, 애니메이션 API(requestAnimationFrame)
👉 서버로 요청 및 응답 → fetch API, AJAX(XHR)
자바스크립트의 3가지 비동기 처리방식
🚩 1. 콜백 함수(Callback Function)
- 콜백 함수(CF, Callback Function)이란?
→ 다른 함수에 매개변수로 넘겨준 함수.
즉, 다른 함수의 매개변수로 함수를 전달하고, 어떠한 이벤트가 발생했을 시 매개변수로 전달된 함수가 다시 호출되는 것을 말한다.
- 콜백(Callback)이란?
→ 어떤 일을 다른 객체에게 시키고, 해당 일이 끝나는 것을 기다리지 않고 끝나고 호출될 때까지 다른일을 수행하는 것. (non-block, 비동기)
- ❔ 왜 콜백 함수(Callback Function)을 사용하는가?
→ JS는 이벤트 기반 언어이다.
→ JS는 비동기적 방식을 사용하는 언어이기에 프로세스가 순차적으로 실행되어야 하는 경우,
작성한 함수가 원치 않는 순서로 실행되어, 결과가 뒤바뀔 수 있다.
즉, 콜백 함수(Callback Function)는 비동기 처리방식의 문제점을 해결해주기 위해 특점 시점에서 호출이 되도록 만드는 함수.
/* 예제 1. */
function A(callback){
console.log("A function");
callback();
}
function B(){
console.log("B function");
}
A(B);
// 결과 : A function B function
// -> Callback Function B를 A의 매개변수로 전달
/* 예제 2. */
function cal(cb, x, y){
return cb(x, y);
}
function add(a, b){
return a+b;
}
console.log(cal(add, 1, 2));
// 결과 : 3
🚩 2. Promise
“A promise is an object that may produce a single value some time in the future”
프로미스(Promise)란? ⇒ 자바스크립트 비동기 처리에 사용되는객체
❓ 비동기 처리란? → 비동기식 동작이 동기적으로 동작하도록 하는 처리.
🤔 Promise가 필요한 이유?
- 프로미스는 주로 서버에서 받아온 데이터를 화면에 표시할 때 사용한다.
- 일반적으로 웹 애플리케이션을 구현할 때 서버에서 데이터를 요청하고 응답받기 위해 아래와 같은 APi를 사용한다.
$.get('{url}/bbs/1', function(response){
...
});
상기의 API는 서버로부터 데이터를 응답받아오는 요청을 보낸다.
만약, 아직 서버로부터 응답을 받아오지 못했는데 데이터를 사용하고자 하면 오류가 발생한다.
⇒ 이를 해결하기 위한 방법 중 하나가 프로미스(Promise)
💡Promise의 기본 문법
const _promise = new Promise((resolve, reject) => {
// 비동기 작업
}
_promise
.then(() => {
console.log("this is then!");
})
.catch(() => {
console.log("this is catch!");
});
then: 해당promise가 성공했을 때의 동작을 지정한다.catch: 해당promise가 실패했을 때의 동작을 지정한다.
promise는약속이다.promise의 선언부는 ‘지금 넘겨줄 데이터가 없으니 추후 주겠다’라는 의미
💡Promise의 상태(State)

pending: 프로세스(promise)를 수행 중인 상태 (fulfill혹은reject가 되기 전)fulfill(resolve): 프로세스(promise)가 완료된 상태reject: 프로세스(promise)가 중단 혹은 실패한 상태settled: 프로세스(promise)가 성공이거나 실패가 되었든 일단 완료한 상태.
// promise 선언부
const _promise = function(param){
return new Promise(function(resolve, reject){
// 비동기를 표현하기 위한 setTimeout func
window.setTimeout(function(){
if(param){
resolve("프로세스 완료");
}
else{
reject(Error("프로세스 실패"));
}
}, 3000);
});
}
// 실행부
_promise(true)
.then(function(data){
console.log(data); // promise 성공
}, function(error){
console.error(error); // promise 실패
});
위 코드를 해석해보자.
new Promise로promise객체가 생성된 후,resolve혹은reject가 호출되기 까지의 순간을pending상태라 할 수 있다.- 비동기 작업(위 코드는
setTimeout을 의미)이 끝난 뒤 실행부에서 인수로 넘긴true가resolve를 호출하고, promise 작업이 실패했을 경우를 구현하기 위해false가 인수로 넘어가면reject가 호출된다. - 실행부에서
_promise()를 호출하면Promise객체가 반환된다.
💡Promise의 then과 catch
/* 상황 1. then과 resolve */
const _promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("resolve되었습니다.");
}, 3000);
});
_promise
.then(value => console.log(value));
// then은 resolve된 값들을 받아오는 역할을 수행한다. (즉, promise(fulfill)가 완료되었을때)
/* 상황 2. catch와 reject */
// resolve가 아닌 reject된 값이 넘어온 경우 then을 사용할 시 에러가 발생한다.
// java의 try-catch와 같은 맥락으로 JS에서도 catch를 통해 reject로 넘어온 에러를 잡을 수 있다.
const _promise = new Promise((resolve, reject) => {
setTimeout(() => {
reject("reject되었습니다.");
}, 3000);
});
_promise
.then(value => console.log(value))
.catch(error => console.log(error));
// 이때, then과 catch가 순차적으로 작성되었다고 해서 then이 수행된 후 catch가 실행되는게 아니다.
// 단순히 결과가 resolve라면 then으로, reject라면 catch로 넘어간다.
/* 상황 3. Chaining Promise와 catch */
// 만약, promise를 여러번 사용하고자 한다면?
// 예시 : promise를 통해 API로 data를 받아온 뒤, 받아온 데이터를 promise를 통해 복호화하는 경우
// 이와 같이, promise를 여러번, 순차적으로 사용하는 경우를 Chaining Promise라 한다.
const _promise = new Promise((resolve, reject) => {
resolve(2);
});
const plus = num => num + 1;
_promise
.then(plus)
.then(plus)
.then(plus)
.then(() => { throw Error("error") })
.then(result => console.log(result))
.catch(error => console.log(error));
// 위와 같이 Chaining Promise를 사용할 때 각각의 then에 모두 catch를 지정할 필요 없이
// 한 번의 catch로 모든 then을 해결할 수 있다.
// Promise 예외처리 문법 정리
try{
// 예외가 발생할 수 있는 명령 작성 (pending)
}catch(error){
// 에러가 발생하였을 때 (reject), error는 에러 객체를 뜻한다.
} finally {
// 예외가 발생하거나, 발생하지 않아도 무조건 실행시킬 명령
// 즉, try가 실행되든 catch가 실행되는 finally는 무조긴 실행된다.
}
🚩 3. Promise를 활용한 async/await
비동기 프로그래밍에서 Callback function을 사용해야한다.
하지만, 콜백함수의 깊이가 깊어질수록 코드가 매우 복잡해지며 가독성이 떨어지는 이른바 콜백지옥 이 발생한다.

위와 같은 콜백지옥 을 해결하기 위해 ES6 에서 Promise 를 도입하였지만, promise가 resolve 되거나 reject 되는 경우를 대비하기 위해 체이닝 프로미스(Chaining promise) 를 작성한다.
아래의 코드를 보자.
function callbackHell(num){
const _promise = new Promise((resolve, reject) => {
setTimeout(() => {
const result = number + 10;
if(result > 50){
const err = new Error("너무 큰 숫자입니다.");
return reject(err);
}
resolve(result);
}, 1000);
})
return _promise;
}
// promise로 넘어가는 props이 50보다 작으면 resolve, 크면 reject
callbackHell(0)
.then((num) => { console.log(num); return callbackHell(num); }) // 10
.then((num) => { console.log(num); return callbackHell(num); }) // 20
.then((num) => { console.log(num); return callbackHell(num); }) // 30
.then((num) => { console.log(num); return callbackHell(num); }) // 40
.then((num) => { console.log(num); return callbackHell(num); }) // 50
.then((num) => { console.log(num); return callbackHell(num); }) // Error
.catch((err) => { console.log(err); });
위와 같이 코드가 직관적으로 들어오지않아 이해하기 힘들다.
이러한 체이닝 프로미스 를 사용할 때 발생되는 불편함을 해결하기위해 ES8 에서 async/await 를 도입하였고, 그 결과 비동기 처리를 직관적이며 깔끔하게 수행할 수 있게 되었다.
function callbackHell(num){
const _promise = new Promise((resolve, reject) => {
setTimeout(() => {
const result = number + 10;
if(result > 50){
const err = new Error("너무 큰 숫자입니다.");
return reject(err);
}
resolve(result);
}, 1000);
})
return _promise;
}
async function startTasks(){
try{
let result = await callbackHell(0);
console.log(result);
result = await callbackHell(result);
console.log(result);
result = await callbackHell(result);
console.log(result);
result = await callbackHell(result);
console.log(result);
result = await callbackHell(result);
console.log(result);
result = await callbackHell(result);
console.log(result);
result = await callbackHell(result);
}catch(err){
console.log(err)
}
}
-
async: async는 항상function앞에 위치한다.async가 수식된 함수는 항상Promise를 반환한다. 만약,Promise가 아닌 값을 반환하더라도 이행 상태 프라미스(resolved promise)로 값을 감싸 resolve된 promise를 반환한다. -
await: await는 반드시async함수 안에서만 동작한다. JS는await를 만나게 되면Promise가 처리될 때까지(resolve) 기다린다. 즉, JS의비동기 방식을동기로 편하게 처리해주는 역할을 수행한다. -
만약,
promise가 거부(reject)를 반환하면 어떻게 해야하나?async function test(){ await Promise.reject(new Error("에러 발생")); } // 위 코드는 아래와 동일하다. async function test(){ throw new Error("에러 발생"); } -
위와 같이 promise가 거부되면
await는 에러를 반환한다. -
await가 던진 에러는try-catch문을 사용하여 처리할 수 있다.
🤔 async와 await은 항상 옳을까?
- 모든 경우에서
async와await가 맞는 방법은 아니다. - callback의 깊이가 그리 깊지 않을 때는 간편한 콜백 함수를 사용하거나 Promise를 사용하는 것이 더 효율적인 방법일 수도 있다.
async와await는Promise를 사용하기 때문에 반드시Promise를 알아야한다.
→ 즉, async/await이 할 수 없는 동작을 단순히 promise로 해결할 수 있는 경우도 존재한다.
⇒ 💡 결론은 시기적절하게 promise와 async/await를 사용하자.
👏 번외 : JS의 fetch API
fetch('api url')
.then(response => response.json())
.then(response => {
// 서버로부터 받아온 데이터를 처리하는 로직 작성
});
- JS에서 지원하는
fetch는 서버로요청(Request)를 보내고응답(Response)을 받을 수 있도록 해주는 메서드이다. - 🤔
XMLHttpRequest와의 차이는?
→ fetch API 는 Promise 기반 메서드이기에 더욱 간편하게 사용할 수 있다는 차이가 있다.
fetch를 호출하면 브라우저는 서버로 요청(Request)을 보낸 뒤Promise객체를 반환한다.- 요청(Request)이 완료되면 호출 성공 여부와는 관련 없이
Promise객체는resolve되어응답(Response)객체가 반환된다. - 요청(Request)가 실패하면
Promise객체는rejected된다. 이 경우catch를 통해 예외처리할 수 있다. (네트워크 이슈 혹은 404 요청) fetch로부터 받아오는응답(Response)객체는 서버에서 응답한 데이터에 대한 정보를 가지고 있다.ok,status속성 등을 이용하여 응답 성공 여부를 확인할 수 있다.CORS,HTTP Origin header semantics와 같은 개념들을 정의하고, 수정할 수 있다.
const serverAPI = async (params) => {
try{
const res = await fetch("API URL");
const result = await res.JSON();
console.log("서버로부터 응답받은 데이터", result);
}catch(error){
console.log("에러발생", error);
}finally{
console.log("async 종료");
}
}
// fetch API 실행
serverAPI();