비동기
1. 동기(synchronous), 비동기 (asynchronous)
동기란, 위의 그래프와 같이 특정 코드의 실행이 완료될 대까지 기다리고 난 후, 다음 것을 실행하는 것을 의미한다. 예를 들어, 밥을 다 먹은 후, 양치를 하고, 양치를 다 한 후, 옷을 갈아입는 것, 이런 식으로 무언가가 끝난 후 다른 것을 하는 것이 동기 처리이다.
그에 반해, 비동기는 특정 코드 실행이 완료되는 것을 기다리지 않고 다음 코드를 실행하는 것이다. 예를 들어, 밥을 먹으면서 tv를 켜서 같이 보는 것이나 길을 걸으면서 노래를 흥얼거리는 것과 같이, 무언가가 끝나는 것을 기다리지 않고 실행하는 것이 비동기 처리이다. 대표적인 비동기 함수로는 setTimeout이 있는데, 간단한 예시는 아래와 같다.
setTimeout(function () {
console.log("1초가 지났습니다!");
}, 1000);
setTimeout은 첫 번쨰 인자로 함수를 받고 두 번째 인자로는 시간(ms단위)를 받는다. 그리고 입력 받은 시간이 지나면 입력 받은 함수를 실행한다. 위의 코드 같은 경우에는 1000ms초, 즉 1초가 지난 후 "1초가 지났습니다!" 라는 문장을 출력하게 된다. 그리고 setTimeout은 메모리 누수가 발생할 수 있다고 한다. 그렇기 때문에 clearTimeout을 이용해 setTimeout을 종료, 제거하는 것이 좋다고 한다.
const id = setTimeout(function () {
console.log("10초가 지났습니다!");
}, 10000);
clearTimeout(id);
clearTimeout은 위와 같이 setTimeout이 반환하는 값을 이용해 그 setTimeout을 종료, 제거할 수 있다.
2. 콜백 (Callback)
콜백은 비동기로 작동하는 코드를 제어하는 방법 중 하나로, 다른 함수의 인자로써 넘겨진 후 특정 이벤트에 의해 호출되는 함수를 의미한다. 콜백을 이용한 예시는 아래와 같다.
const sayName = (name, callback) => {
setTimeout(function () {
console.log(name);
callback();
}, Math.floor(Math.random() * 100) + 1);
};
console.log("사람들의 이름을 부릅니다!");
sayName("James", () => {
sayName("John", () => {
sayName("Thomas", () => {});
});
});
위의 sayName 함수는 첫 번쨰 인자로 name을 받고 두 번째로는 callback 함수를 받는다. 그리고 setTimeout을 이용해 랜덤한 시간이 지난 후 이름을 출력하고 callback 함수를 실행한다. 이러한 방식을 통해 비동기를 제어할 수 있다. 하지만, callback을 너무 과하게 이용하면 코드가 복잡하고 난잡하게 보일 수 있으며 가독성이 낮아지는 Callback Hell이 발생할 수도 있다.
3. 프로미스 (Promise)
Promise 또한 비동기를 제어하는 방법 중 하나이다. Promise는 class이기 때문에 new 키워드를 이용해 Promise 객체를 생성한다, 또한, Promise 객체는 비동기 처리를 수행할 callback 함수를 인수로 받는다. 이 callback 함수는 인수로 resolve, reject 함수를 받는다. resolve의 경우, 입력 받은 함수가 정상적으로 처리되었을 때 호출되며 reject는 에러가 발생하였을 때 호출하게 (작성하면) 된다.
Promise 객체의 State에는 pending(대기), fulfilled(이행), rejected(거부, 에러 발생 시)가 있다. 그리고 Promise는 then, catch, fnally 메서드를 가진다. then은 작성된 callback 함수의 코드들이 정상적으로 처리되었을 때, resolve를 호출하고 then 메서드로 접근할 수 있다. 그리고 catch는 callback 함수의 코드들이 에러가 발생했을 경우, reject를 호출하고 catch 메서드로 접근할 수 있다. 마지막으로 finally는 성공하든 실패하든 접근할 수 있다.
const sayName = (name) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve();
console.log(name);
}, Math.floor(Math.random() * 100) + 1);
});
};
sayName("홍길동")
.then(() => {
return sayName("영희");
})
.then(() => {
return sayName("철수");
});
이 코드의 경우, 우선 "홍길동"을 출력한다. 그리고 성공적으초 처리가 되었다면 then에 있는 '() => { return sayName("영희") };'를 실행한다. 그리고 이 역시 성공한다면 뒤의 '() => { return sayName("철수") };'를 실행한다.
const sayName = (name) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log(name);
resolve("이름 불렀음!");
}, Math.floor(Math.random() * 100) + 1);
});
};
sayName("홍길동").then((result) => {
console.log(result);
});
// 출력 ⬇️
// 홍길동
// 이름 불렀음!
그리고 resolve는 다음과 같이 확인할 수 있다. 그리고, Promise 또한 Callback과 마찬가지로 Promise Hell이 생길 수 있다.
4. Async/Await
Async와 Await은 ES8에서 제공된 키워드로, 복잡해질 수 있는 Callback이나 Promise를 간결하게 작성할 수 있게 한다. 이를 사용하기 위해서는 함수 앞에 async 키워드를 사용하고, 이 함수 안에서만 await 키워드를 사용하면 된다. 예시는 다음과 같다.
const sayName = (name) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve("이름 불렀음!");
console.log(name);
}, Math.floor(Math.random() * 100) + 1);
});
};
const SayAllNames = async () => {
await sayName("홍길동");
await sayName("영희");
await sayName("철수");
const result = await sayName("James");
console.log(result);
};
SayAllNames();
// 출력 ⬇️
// 홍길동
// 영희
// 철수
// James
// 이름 불렀음!