
JavaScript의 동기 처리란 ‘특정 코드의 실행이 완료될 때까지 기다리고 난 후 다음 코드를 수행하는 것’을 의미한다. 주문 즉시 붕어빵을 만들어 주는 노점상이 있다고 생각해보자. 동기적으로 운영되는 노점상의 경우 붕어빵을 주문받은 후 주문받은 붕어빵이 다 만들어지고 난 후에야 다음 손님의 주문을 받고 붕어빵을 제작하게 된다. 이 경우 여러 손님의 주문을 소화하기에는 무리가 있다.

avaScript의 비동기 처리는 ‘특정 코드의 실행이 완료될 때까지 기다리지 않고 다음 코드들을 수행하는 것’을 의미한다. 앞선 예시로 든 노점상이 비동기적으로 운영되는 경우 여러 손님의 주문을 받으면서 붕어빵을 제작하게 되고 완성되는 대로 손님에게 붕어빵을 제공하게 된다. 동기적으로 운영하는 경우보다 훨씬 효율적이다.
Callback은 ES6에서 Promise가 표준화될 때까지 비동기를 처리하는 공식 방법이였다.
비동기를 호출하는 함수를 호출하면서 콜백 함수라는 인자를 넣어 함수의 결과물을 필요로 하는 뒤의 로직을 구성할 수 있게 된다.
const printString = (string, callback) => {
setTimeout(function () {
console.log(string);
callback();
}, 1000);
};
const printAll = () => {
printString('A', () => {
printString('B', () => {
printString('C', () => {});
});
});
};
setTimeout(callback,time) 함수는 브라우저 환경과 Node.js 환경에서 사용할 수 있는 JavaScript 함수 중 하나로, 일정 시간(time)이 지난 후에 콜백 함수(callback)를 실행하도록 예약하는 기능을 제공한다. 즉, setTimeout 함수는 브라우저나 Node.js에서 비동기적으로 작동하는 함수다.
Callback 함수를 통해 비동기 코드의 순서를 제어할 수 있지만 코드가 길어질 수록 복잡해지고 가독성이 낮아지는 Callback Hell이 발생하는 단점이 있다.
const printString = (string, callback) => {
setTimeout(function () {
console.log(string);
callback();
}, 1000);
};
const printAll = () => {
printString('A', () => {
printString('B', () => {
printString('C', () => {
printString('D', () => {
printString('E', () => {
printString('F', () => {
printString('G', () => {
printString('H', () => {
printString('I', () => {
printString('J', () => {
printString('K', () => {
printString('L', () => {
printString('M', () => {
printString('N', () => {
printString('O', () => {
printString('P', () => {});
});
});
});
});
});
});
});
});
});
});
});
});
});
});
});
};
printAll();
비동기로 작동하는 코드를 제어할 수 있는 다른 방법은 Promise를 활용하는 방법이다. 또한 Callback Hell을 방지하는 역할도 수행한다.
Promise는 class이기 때문에 new 키워드를 통해 Promise 객체를 생성한다. 또한 Promise는 비동기 처리를 수행할 콜백 함수(executor)를 인수로 전달받는데 이 콜백 함수는 resolve, reject 함수를 인수로 전달받는다.
Promise 객체가 생성되면 executor는 자동으로 실행되고 작성했던 코드들이 작동된다. 코드가 정상적으로 처리가 되었다면 resolve 함수를 호출하고 에러가 발생했을 경우에는 reject 함수를 호출하면 된다.
let promise = new Promise((resolve, reject) => {
// 1. 정상적으로 처리되는 경우
// resolve의 인자에 값을 전달할 수도 있다.
resolve(value);
// 2. 에러가 발생하는 경우
// reject의 인자에 에러메세지를 전달할 수도 있다.
reject(error);
});
new Promise가 반환하는 Promise 객체는 state, result 내부 프로퍼티를 갖는다. 하지만 직접 접근할 수 없고 .then, .catch, .finally의 메서드를 사용해야 접근이 가능하다.

프로미스가 정상적으로 처리된 경우의 프로미스 객체

프로미스가 에러가 발생한 경우의 프로미스 객체
State
기본 상태는 pending(대기) 이다. 비동기 처리를 수행할 콜백 함수(executor)가 성공적으로 작동했다면 fulfilled(이행)로 변경이 되고, 에러가 발생했다면 rejected(거부)가 된다.
Result
처음은 undefined 이다. 비동기 처리를 수행할 콜백 함수(executor)가 성공적으로 작동하여resolve(value)가 호출되면 value로, 에러가 발생하여 reject(error)가 호출되면 error로 변한다.
.then 메서드로 접근할 수 있다. 또한 .then 안에서 리턴한 값이 Promise면 Promise의 내부 프로퍼티 result를 다음 .then 의 콜백 함수의 인자로 받아오고, Promise가 아니라면 리턴한 값을 .then 의 콜백 함수의 인자로 받아올 수 있다.let promise = new Promise((resolve, reject) => {
resolve("성공");
});
promise.then(value => {
console.log(value);
// "성공"
})
.catch 메서드로 접근할 수 있다.let promise = new Promise(function(resolve, reject) {
reject(new Error("에러"))
});
promise.catch(error => {
console.log(error);
// Error: 에러
})
.finally 메서드로 접근할 수 있다.let promise = new Promise(function(resolve, reject) {
resolve("성공");
});
promise
.then(value => {
console.log(value);
// "성공"
})
.catch(error => {
console.log(error);
})
.finally(() => {
console.log("성공이든 실패든 작동!");
// "성공이든 실패든 작동!"
})
Promise chaining가 필요하는 경우는 비동기 작업을 순차적으로 진행해야 하는 경우다. Promise chaining이 가능한 이유는 .then, .catch, .finally 의 메서드들은 Promise를 리턴하기 때문다. 따라서 .then을 통해 연결할 수 있고, 에러가 발생할 경우 .catch 로 처리하면 된다.
let promise = new Promise(function(resolve, reject) {
resolve('성공');
...
});
promise
.then((value) => {
console.log(value);
return '성공';
})
.then((value) => {
console.log(value);
return '성공';
})
.then((value) => {
console.log(value);
return '성공';
})
.catch((error) => {
console.log(error);
return '실패';
})
.finally(() => {
console.log('성공이든 실패든 작동!');
});
Promise.all()은 여러 개의 비동기 작업을 동시에 처리하고 싶을때 사용한다.
인자로는 배열을 받는다. 해당 배열에 있는 모든 Promise에서 executor 내 작성했던 코드들이 정상적으로 처리가 되었다면 결과를 배열에 저장해 새로운 Promise를 반환 해준다.
const promiseOne = () => new Promise((resolve, reject) => setTimeout(() => resolve('1초'), 1000));
const promiseTwo = () => new Promise((resolve, reject) => setTimeout(() => resolve('2초'), 2000));
const promiseThree = () => new Promise((resolve, reject) => setTimeout(() => resolve('3초'), 3000));
// 기존
const result = [];
promiseOne()
.then(value => {
result.push(value);
return promiseTwo();
})
.then(value => {
result.push(value);
return promiseThree();
})
.then(value => {
result.push(value);
console.log(result);
// ['1초', '2초', '3초']
})
// promise.all
Promise.all([promiseOne(), promiseTwo(), promiseThree()])
.then((value) => console.log(value))
// ['1초', '2초', '3초']
.catch((err) => console.log(err));
추가적으로 Promise.all()은 인자로 받는 배열에 있는 Promise 중 하나라도 에러가 발생하게 되면 나머지 Promise의 state와 상관없이 즉시 종료된다.
JavaScript는 ES8에서 async/await키워드를 제공하였다. 이를 통해 복잡한 Promise 코드를 간결하게 작성할 수 있게 되었다. 사용법은 함수 앞에 async 키워드를 사용하고 async 함수 내에서만 await 키워드를 사용하면 된다. 이렇게 작성된 코드는 await 키워드가 작성된 코드가 동작하고 나서야 다음 순서의 코드가 동작하게 된다.
// 함수 선언식
async function funcDeclarations() {
await 작성하고자 하는 코드
...
}
// 함수 표현식
const funcExpression = async function () {
await 작성하고자 하는 코드
...
}
// 화살표 함수
const ArrowFunc = async () => {
await 작성하고자 하는 코드
...
}