먼저 비동기 프로그래밍을 알아보기 전에 동기 처리와 비동기 처리에 대해서 알아보자
- 동기적으로 작업이란?
-> 순서가 보장 된 처리 방식, 다른 작업을 완료 전 까지 다른 작업을 블로킹(작업 중단)
하는 처리 방식- 비동기적으로 작업이란?
-> 순서가 보장 되지 않은 처리 방식, 다른 작업이 완료 되기 전에도 블로킹(작업 중단)
하지 않고 다음 작업을 실행하는 처리 방식
간단한 예시로 동기적 처리 방식에 대해 알아보자
function foo () {
console.log("foo");
}
function bar () {
console.log("bar");
}
foo(); // foo
bar(); // bar
위와 같이 어떠한 작업이 끝이 나고 나서 그 다음 작업을 실행 순차적 실행
간단한 예시로 비동기적 처리 방식에 대해 알아보자
// 위와 같이 실행을 하되 setTimeout으로 바꾸어 실행 해보자
function foo () {
console.log("비동기에서 foo");
}
function bar () {
console.log("비동기에서 bar");
}
setTimeout(foo, 0);
bar();
실행을 해보면 비동기에서 bar ,비동기에서 foo 이렇게 나오게 된다.
왜 foo가 먼저 실행이 되지 않고 bar가 먼저 실행이 될까?
setTimeout(foo, 0)
가 foo
를 비동기적으로 실행 되도록 예약bar
는 동기적으로 실행bar
함수가 실행 후 출력bar
함수가 실행이 완료 되었기에 스택(Stack)이 비어진 상태foo
함수가 비어 있는 스택(Stack)으로 이동foo
함수가 실행 후 출력이러한 과정을 거치기에
setTimeout
이 0초라고 하더라도 바로 실행 되지 않는 현상이다.
함수를 호출할 때, 해당 함수의 정보(변수, 매개 변수 등) 스택에 푸쉬(push),
함수가 실행이 완료 되면 해당 정보를 스택에서 팝(pop)되어 제거 -> 스택이 비어짐
Escape Callback Hell🔥
연속적으로 발생하는 비동기 함수를 처리할 때, 비동기 함수의 결과를 사용하기 위해 콜백 함수가
중첩되어 복잡해지는 현상을 해결해보자
resolve
reject
a, b의 값을 더하고 그 값을 콜백함수에 전달해서 X2를 하고, 또 다시 -X2를 하는 함수를 콜백함수로 나타냈다.
function taskA(a, b, cb) {
setTimeout(() => {
const res = a + b;
cb(res);
}, 1000);
}
function taskB(a, cb) {
setTimeout(() => {
const res = a * 2;
cb(res);
}, 1000);
}
function taskC(a, cb) {
setTimeout(() => {
const res = a * -2;
cb(res);
}, 1000);
}
taskA(4, 4, (a_res) => {
console.log("taskA : ", a_res); // taskA : 8
taskB(a_res, (b_res) => {
console.log("taskB : ", b_res); // taskB : 16
taskC(b_res, (c_res) => {
console.log("taskC : ", c_res); // taskC : -32
});
});
});
흠 보기만 해도 조금 복잡하기도 하고 이게 줄이 더 길어지면 더 복잡해 질 것이다.
위의 코드를 promise를 통해 코드를 고쳐보자
new Promise((resolve, reject) => {})
- 비동기 작업이 성공 했을 때
(...resolve)
호출- 비동기 작업이 실패 했을 때
(...reject)
호출
그럼 아까 코드를 수정 해보자
function taskA(a, b) {
return new Promise((resolve, reject) => {
setTimeout(() => {
const res = a + b;
resolve(res);
}, 1000);
});
}
function taskB(a) {
return new Promise((resolve, reject) => {
setTimeout(() => {
const res = a * 2;
resolve(res);
}, 1000);
});
}
function taskC(a) {
return new Promise((resolve, reject) => {
setTimeout(() => {
const res = a * -2;
resolve(res);
}, 1000);
});
}
taskA(4, 4)
.then((a_res) => {
console.log("taskA : ", a_res);
return taskB(a_res);
})
.then((b_res) => {
console.log("taskB : ", b_res);
return taskC(b_res);
})
.then((c_res) => {
console.log("taskC : ", c_res);
});
promise
를 통해 코드를 개선하였는데 어떠한 점이 이점인지에 대해 알아보자
promise
를 사용함으로 콜백 함수를 직접 전달하지 않아도 된다는 점then
체이닝의 사용 : then
을 사용함으로 코드의 가독성이 올라갔다는 점const 코드끊기 = taskA(4, 4).then((a_res) => {
console.log("taskA : ", a_res);
return taskB(a_res);
});
console.log("a")
코드끊기
.then((b_res) => {
console.log("taskB : ", b_res);
return taskC(b_res);
})
.then((c_res) => {
console.log("taskC : ", c_res);
});
또한 콜백함수와 달리 중간에 코드를 나눠 어떠한 작업을 추가 할 수 있다는 점이 있다.
그렇지만 아직 복잡한 경향이 있다 그렇기에 이번에는 async
, await
를 통해 코드를 개선해보자
Escape CallbackHell🔥, Escape thenHell🔥
async function makeAsync() {
return "Make Async";
}
console.log(makeAsync()); // Promise {<pending>}
async
를 붙인 함수makeAsync
의 결과값이 promise
로 나오는 것을 볼 수 있다
-> then
을 사용 가능한 비동기 처리 함수가 된다
async function makeAsync() {
return "Make Async";
}
makeAsync().then((res) => {
console.log(res); // Make Async
});
return
한 값인 "Make Async"가 resolve
값으로 쓰이는 것을 알 수 있다.
function delay(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
async function makeAsync() {
await delay(3000);
return "Make Async";
}
makeAsync().then((res) => {
console.log(res); // 3초 뒤Make Async
});
await
비동기 함수 앞에 붙이게 되면 비동기 함수를 마치 동기 함수로 사용 할 수 있게 된다.await
함수가 끝나고 나서 뒤에 함수가 실행이 된다.await
함수는 async
가 붙은 함수 내에서만 사용 가능하다.function delay(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
async function taskA(a, b) {
await delay(1000);
const res = a + b;
return res;
}
async function taskB(a) {
await delay(1000);
const res = a * 2;
return res;
}
async function taskC(a) {
await delay(1000);
const res = a * -2;
return res;
}
async function main() {
try {
const a_res = await taskA(4, 4);
console.log("taskA : ", a_res);
const b_res = await taskB(a_res);
console.log("taskB : ", b_res);
const c_res = await taskC(b_res);
console.log("taskC : ", c_res);
} catch (error) {
console.log("Error : ", error);
}
}
main();
async
, await
를 사용하여 코드를 수정 했는데 확실히 어떠한 작업인지 구분이 간다await
을 통하여 비동기 처리 방식 내에서 그 결과를 기다리는 방식으로 작동이 된다.main
함수를 통하여 작업이 순서대로 수행되고, 에러핸들링이 된다.