실전 리팩토링은 항상 어려움으로 다가온다.
다른 사람의 코드를 많이 보고 리뷰하고 좋은 것을 내 것으로 만드는 것이 가장 빠른 길이라고 생각한다.
그래서 요새 공식 문서를 읽는 것 이외에도 다양한 컨퍼런스를 통해 학습을 하고 있다.
이 영상은 원티드 운영진분들이 강력하게 추천해주시는 영상이다!
도움이 너무너무 많이 됐기 때문에 기록으로 남겨둔다.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>실전 Try-catch문</title>
<script>
// 미리 로딩해둘 이미지 이름 배열
const imgs = [
{ name: 'HEART', url: 'https://placeimg.com/640/480/animal' },
{ name: '6', url: 'https://placeimg.com/640/500/animal' },
{ name: '하트', url: 'https://placeimg.com/640/520/animal' },
{ name: '도넛', url: 'https://placeimg.com/640/540/animal' },
];
const loadImage = (url) =>
new Promise((resolve) => {
let img = new Image();
img.src = url;
img.onload = function () {
resolve(img);
};
return img;
});
// generator
function* map(f, iter) {
for (const a of iter) {
yield a instanceof Promise ? a.then(f) : f(a);
}
}
async function reduceAsync(f, acc, iter) {
for await (const a of iter) {
acc = f(acc, a);
}
return acc;
}
loadImage(imgs[0].url).then((img) => console.log(img.height));
async function f1() {
try {
let error = null;
const total = await imgs
.map(async ({ url }) => {
if (error) return;
try {
const img = await loadImage(url);
return img.height;
} catch (e) {
error = e;
console.log(e);
throw e;
}
})
.reduce(async (total, height) => (await total) + (await height), 0);
console.log('f1?', total);
} catch (e) {
console.log(e);
}
}
// async function f2() {
// for await (const a of map(
// (img) => img.height,
// map(({ url }) => loadImage(url), imgs)
// )) {
// console.log(a);
// }
// }
// 아래와 같이 구현하면 error를 발생시킬 수 있다 -> try catch문 사용 가능
async function f2() {
console.log(
'f2?',
await reduceAsync(
(a, b) => a + b,
0,
map(
(img) => img.height,
map(({ url }) => loadImage(url), imgs)
)
)
);
}
async function f3() {
try {
console.log(
await reduceAsync(
(a, b) => a + b,
0,
map(
(img) => img.height,
map(({ url }) => loadImage(url), imgs2)
)
)
);
} catch (e) {
console.log('서버에 에러 전달', e);
console.log(0);
}
}
f1();
f2(); // 정상 작동
f2().then(console.log('!'));
f3(); // error
// 에러 핸들링을 하지 않는 코드가 BEST
async function f4(imgs) {
return await reduceAsync(
(a, b) => a + b,
0,
map(
(img) => img.height,
map(({ url }) => loadImage(url), imgs)
)
);
}
// 더 간결해지는 코드 - 표현식, 순수함수
// 에러의 여지가 없다 imgs인자가 정확한 값이면 에러가 날 수 없음
// 특별한 에러핸들링을 위해 if, try-catch를 사용하는 코드는 좋지 않음
// 데이터가 특별할 수 있음(아예 에러의 가능성이 없는 데이터 or 에러의 가능성이 있거나 없는 데이터)
// 에러를 터뜨릴 수 있도록 만들어놓는 함수가 좋은 함수
// 어떤 개발자는 에러가 찍히길 원하고 다른 개발자는 에러가 찍히지 않는 걸 원할 수도 있다
const f5 = (imgs) =>
reduceAsync(
(a, b) => a + b,
0,
map(
(img) => img.height,
map(({ url }) => loadImage(url), imgs)
)
);
f5(imgs).then(console.log('f5 successed!'));
f5(imgs2)
.catch((_) => 0)
.then(console.log('f5 successed!'));
</script>
</head>
<body>
<h3>ES6+ 비동기 프로그래밍과 실전 에러 핸들링</h3>
<h4>Console을 확인해보세요</h4>
</body>
</html>
정리
- Promise, async/await, try/catch를 정확히 다루는 것이 중요합니다.
- 제너레이터/이터레이터/이터러블을 잘 응용하면 코드의 표현력을 더할 뿐 아니라 에러 핸들링도 더 잘할 수 있습니다.
- 순수 함수에서는 에러가 발생되도록 그냥 두는 것이 더 좋습니다.
- 에러 핸들링 코드는 부수효과를 일으킬 코드 주변에 작성하는 것이 좋습니다.
- 불필요하게 에러 핸들링을 미리 해두는 것은 에러를 숨길 뿐입니다.
- 차라리 에러를 발생시키는 게 낫습니다.
setry.io같은 서비스를 이용하여 발생되는 모든 에러를 볼 수 있도록 하는 것이 고객과 회사를 위하는 더 좋은 해법입니다.
함수 f1부터 f5까지 천천히 보면서 더 효율적인 코드가 무엇인 지 고민해보자.
필요에 따라 선택을 달리할 수 있지만 f1()을 사용하는 것은 이제 벗어날 때라고 생각한다:)