javacript는 싱글스레드 언어
- 코드를 동기적으로 처리
- 만약 5초가 걸리는 API 요청을 하게 된다면 5초 동안은 다른일을 하지 못하기 때문에 비효율적인 문제 발생
- 이러한 문제점을 해결할 수 있는 방법이
비동기 기술
// Cabllback을 이용한 방법
const countUp = (count, callback) => {
setTimeout(() => callback(count + 1), 1000);
};
countUp(1, (count) => {
countUp(count, (count) => {
countUp(count, (count) => {
countUp(count, (count) => {
// 위에서 callback 함수를 호출하는 곳에서 아무것도 할 수 없으므로
// 여기서 다른 작업을 처리해야함
console.log(count);
});
});
});
});
Callback을 이용해 비동기 처리를 할 경우 문제점
콜백 지옥
- 비동기 처리 결과를 외부에 반환하지 못함
- 가독성이 떨어짐
- 에러가 발생하기 쉽지만 그 에러를 추적하기는 어려움
- 위의 이유들로 인해 유지/보수가 어려움
// Promise를 이용한 방법
const countUpPromise = (count) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (count) resolve(count + 1);
else reject(new Error("not Count"));
}, 1000);
});
};
const promiseReturnValue = countUpPromise(1)
.then((count) => countUpPromise(count))
.then((count) => countUpPromise(count))
.then((count) => countUpPromise(count))
.then((count) => console.log(count))
.catch((err) => console.log(err));
console.log(promiseReturnValue);
Callback에서 보완된 점
콜백 지옥
에서 벗어남- Callback 에서는 비동기 처리만 했지만 Promise는
일급 객체
로써 대기(pending)와 성공(fulfilled)과 실패(rejected)일급 값
을 통해 비동기 처리와 더불어값 반환
도 할 수 있음- Callback을 통해 네트워크 통신을 할 때 문제 발생에 대비해 예외 처리 코드를 콜백 함수마다 넣어주게되면 가독성 및 유지/보수 어려움 => Promise의 메서드를 통해 문제점 해결할 수 있음
Promise를 이용해 비동기 처리를 할 경우 문제점
- 여전히 체인 속에 작업이 묶여 있음
- 비동기 작업의 순서 보장이 안됨
// Promise의 문제점
const fetchComment = () => {
return new Promise((resolve) => {
setTimeout(() => {
fetch("https://jsonplaceholder.typicode.com/comments")
.then((res) => res.json())
.then((data) => resolve(data));
}, 3000);
});
};
const fetchPost = () => {
return new Promise((resolve) => {
setTimeout(() => {
fetch("https://jsonplaceholder.typicode.com/posts")
.then((res) => res.json())
.then((data) => resolve(data));
}, 1000);
});
};
// 코멘트 먼저 불러오고 싶은데 post가 먼저 fetch 된다.
fetchComment().then((data) => console.log("commentData", data));
fetchPost().then((data) => console.log("postData", data));
function* genFunc() {
try {
setTimeout(() => {
fetch("https://jsonplaceholder.typicode.com/comments")
.then((res) => res.json())
.then((data) => {
console.log(data);
});
}, 3000);
yield 1;
setTimeout(() => {
fetch("https://jsonplaceholder.typicode.com/posts")
.then((res) => res.json())
.then((data) => {
console.log(data);
});
}, 1000);
} catch (e) {
console.error(e);
}
yield 2;
}
const generator = genFunc();
generator.next();
Promise에서 보완된 점
- 함수의 제어권을 함수가 독점하는 것이 아니라 함수 호출자에게 양도(yield)
generator를 이용해 비동기 처리를 할 경우 문제점
- 값 반환 및 제어권을 넘겨받으려면
next()
필요
generator와 같이 사용하면 좋은 것 => co 라이브러의 co, wrap 함수
// co 함수에 제너레이터를 인수로 넘기면 제너레이터를 마지막까지 실행
// 실행결과로 Promise 반환
co(function* () {
const id = yield getId('010-1234-5678');
const email = yield getEmail(id);
const name = yield getName(email);
return yield order(name, 'coffee');
}).then(result => {
console.log(result);
});
// wrap 함수를 이용해 제너레이터 함수를 Promise를 반환하는 함수로 변환
const orderCoffee = co.wrap(function *() {
const id = yield getId('010-1234-5678');
const email = yield getEmail(id);
const name = yield getName(email);
return yield order(name, 'coffee');
});
orderCoffee.then(result => {
console.log(result);
});
// async, await
const fetchCommentAsync = () => {
return new Promise((resolve) => {
setTimeout(() => {
fetch("https://jsonplaceholder.typicode.com/comments")
.then((res) => res.json())
.then((data) => resolve(data));
}, 3000);
});
};
const fetchPostAsync = () => {
return new Promise((resolve) => {
setTimeout(() => {
fetch("https://jsonplaceholder.typicode.com/posts")
.then((res) => res.json())
.then((data) => resolve(data));
}, 1000);
});
};
const renderData = async () => {
console.log(await fetchCommentAsync());
console.log(await fetchPostAsync());
};
renderData();
// co 라이브러리 wrap 대신 async 사용
function delay(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
async function getPizza() {
await delay(2000);
return "pizza";
}
async function getChicken() {
await delay(1000);
return "chicken";
}
async function pickFoods() {
try {
const pizza = await getPizza();
const chicken = await getChicken();
return `${pizza} + ${chicken}`;
} catch(){
}
}
pickFoods().then(console.log);
generate에서 보완된 점
- async는 `co 라이브러리의 wrap 함수 역할
- await는
yield
역할 => 값을반환
하고제어권
을 넘긴다.- 비동기 작업이지만 동기 작업처럼 소스코드를 작성할 수 있어 가독성이 좋다.
적절한 promise 사용
- async, await 대신 promise를 사용해도 될때가 있다.
- 해당 소스코드는 순서보장을 하지 않아도 되므로 promise의 all 함수를 사용하면 좀더 간결하게 표현할 수 있어 가독성이 좋아질 것 같다.(멘토님 추천)