일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- 쌓임맥락
- vue 이벤트 수신
- netlify redirect
- react next
- vue mixin
- flex
- SCSS extend
- nextjs사용법
- SCSS use
- 폼 입력 바인딩
- 리액트
- postcss
- 프로그래머스 데브코스
- vue 지역 컴포넌트
- intersection opserver
- 리스트 렌더링
- Vue
- SCSS import
- 이벤트 수식어
- 프로그래머스 프론트엔드 데브코스
- KDT 프로그래머스
- git 같은계정 다른 컴퓨터
- Spacer
- KDT 프로그래머스 데브코스 프론트엔드
- 다른컴퓨터에서 git사용
- 고양이 사진 검색기
- 프로그래머스 K_Digital Training
- vuex map
- 프로그래머스 데브코스 프론트엔드
- SCSS forward
- Today
- Total
혼자 적어보는 노트
프로그래머스 데브코스 TIL - Day 10 본문
✅ 학습 목차
- [DAY 10] VanillaJS를 통한 자바스크립트 기본 역량 강화 (2)
- 함수형 프로그래밍과 ES6+ (4)
✅ 새롭게 학습한 부분
- 값으로써의 promise 활용
- 함수 합성 / 모나드 / Kleisli Composition
- 함수의 비동기 제어
- 지연평가 + Promise의 효율성
- 지연된 함수의 병렬 평가 (Concurrency)
- CallStack에 쌓인 catch 에러 해결
💻 비동기/ 동시성 프로그래밍
promise와 callback의 차이란?
promise는 대기, 성공, 실패의 값을 만들고 프로미스 객체가 리턴된다.
값으로써 다루어지기 때문에 일급이다.
const delay100 = a => new Promise(resolve =>
setTimeout(() => resolve(a), 100));
const go1 = (a, f) => a instanceof Promise ? a.then(f) : f(a);
// 전달된 값이 프로미스라면 than메소드와 함께 함수를 실행하고
// 아니라면 전달된 함수에 값을 전달한다.
const add5 = a => a + 5;
console.log(go1(10, add5)) // 15
console.log(go1(delay100(10), add5)) // Promise {<pending>}
go1(delay100(10), add5).then(console.log) // 15
promise를 사용할 경우 값이 담긴 Promise 객체를 리턴한다.
go1 함수를 사용하여 일반 함수와 Promise를 똑같이 사용하고싶다면
아래와 같이 작성을 하면 된다.
go1(go1(10, add5), console.log); // 15
go1(go1(delay100(10), add5), console.log); // 15
함수 합성
const g = a => a + 1;
const f = a => a * a;
console.log(f(g(1))); // 4
console.log(f(g())); // NaN
함수 합성 시 빈 값이 전달되면 잘못된 값이 나오기 때문에 안전한 합성이 아니다.
모나드의 안전한 함수 합성
- 실제 사용자에게 필요한 효과를 적용하기 전(출력 등)까지 안전하게 함수들을 합성하는 기법
[1].map(g).map(f).forEach(r =>console.log(r)); // 4
[].map(g).map(f).forEach(r => console.log(r)); // 아무 값도 없음
[1, 2, 3].map(g).map(f).forEach(r => console.log(r)); // 4 9 6
array를 사용하여 빈 값을 전달할 경우에도 안전하게 결과를 만들 수 있다.
promise의 안전한 합성
Promise.resolve(1).then(g).then(f).then(r => console.log(r)); // 4
Promise.resolve().then(g).then(f).then(r => console.log(r)); // NaN
new Promise(resolve =>
setTimeout(() => resolve(2), 100)
).then(g).then(f).then(r => console.log(r)); // 9
Promise는 then 메소드를 사용해서 합성을 하는데
빈 값을 넣었을 때 잘못된 값이 나타난다.
Promise는 내부에 값이 있고 없고에 대한 안전한 합성이 아니라
비동기 상황(대기)에서 연속적으로 안전한 합성이 가능하게 만들어 준다.
즉, Promise는 합성의 관점에서 보았을 때 비동기적인 프로그래밍에서 안전한 합성을 해준다는 의미를 가진다.
Kleisli Composition 관점에서의 Promise
- 오류가 있을 수 있는 함수의 합성을 안전하게 할 수 있는 하나의 규칙
- 함수를 합성 했을 때 한쪽의 함수에서 에러가 발생 했을 때 같은 결과가 나타나는 것.
ex) f(g(x)) = g(x)
var users = [
{ id: 1, name: "Jay"},
{ id: 2, name: "Hey"},
{ id: 3, name: "May"},
];
const getUserId = id => {
console.log(find(u => u.id == id, users), "id")
return find(u => u.id == id, users) || Promise.reject("값이 없습니다.")
};
const f = ({ name }) => name;
const g = getUserId;
const fg = id => Promise.resolve(id).then(g).then(f).catch(a => a);
// f와 g를 합성한 fg함수
fg(5).then(console.log); // 값이 없습니다.
g의 결과가 없다면 Promise에서 reject를 반환하고
.catch를 사용하여 이후의 연결되는 함수를 진행하지 않고
바로 catch문을 진행함으로써 f(g(x)) = g(x)가 성립된다.
이러한 결과로 인해 promise는 Kleisli Composition을 지원한다고 볼 수 있다.
go, reduce 함수의 비동기제어
const go1 = (a, f) => a instanceof Promise ? a.then(f) : f(a);
const reduce = curry((f, acc, iter) => {
if (!iter) {
iter = acc[Symbol.iterator]();
acc = iter.next().value;
}
return go1(acc, function recur(acc){
let cur;
while(!(cur = iter.next()).done){
const a = cur.value;
acc = f(acc, a);
if (acc instanceof Promise) return acc.then(recur);
}
return acc;
} );
});
const go = (...args) => reduce((a, f) => f(a), args);
go(
1,
a=> a + 100,
a=> Promise.resolve(a + 1000),
a=> a + 1000,
console.log // 2101
)
go 함수에서 Promise를 반환하는 값을 사용할 경우 다음 평가에서 오류가 생기게 되는데
중간에 Promise를 반환하는 함수를 만났을 때 reduce에서 재귀함수를 사용하여
Promise를 처리하고 go1함수를 사용하여 처음에 들어오는 acc값을 처리 함으로써
비동기나 동기 값이 들어오더라도 효율적으로 처리가 되게 할 수 있다.
promise.then의 규칙
Promise.resolve(Promise.resolve(1)).then(console.log) // 1
지연평가
Promise 값을 지연평가 할 경우 오류가 생기는 부분 수정해보기!
go(
[Promise.resolve(1), Promise.resolve(2), Promise.resolve(3)],
L.map(a => a + 10),
take(3),
console.log
);
Promise 값을 반환 받았을 때에도 정상적으로 실행을 할 수 있게
중간에 처리하는 함수들의 코드를 수정해주어야 한다.
L.map = curry(function* (f, iter) {
for (const a of iter) {
// yield f(a);
yield go(a, f);
}
});
const take = curry((l, iter) => {
let res = [];
console.log(iter);
iter = iter[Symbol.iterator]();
return function recur(){
let cur;
while (!(cur = iter.next()).done) {
const a = cur.value;
if(a instanceof Promise) return a.then(a=> {
return (res.push(a), res).length === l ? res : recur();
// a가 Promise라면 then으로 꺼내서 push
// 이미 return이 된 상태기 때문에 재귀를 사용하여 while문을 다시 실행시킨다.
})
res.push(a);
if (res.length === l) return res;
}
return res;
}();
});
L.map함수는 이전에 만든 go1함수로 처리를 하고
reduce에서 진행한 것과 같이 take함수에서는 Promise라면 then으로 꺼내서 담고,
재귀함수로 while문을 다시 실행시켜서 나머지 값들도 담을 수 있다.
* (res.push(a), res).length 처럼 두 개의 내용을 한 줄로 처리할 수 있다는 것을 알게되었다.
filter의 지연을 위한 kleisli composition (+ nop)
go(
[1,2,3,4,5],
L.map(a => Promise.resolve(a * a)),
L.filter(a=> a % 2),
L.map(a => a * a),
take(3),
console.log
)
L.filter 또한 Promise 값이 들어올 경우 잘못된 값이 출력된다.
const nop = Symbol('nop');
L.filter = curry(function* (f, iter) {
for (const a of iter) {
const b = go1(a, f);
if(b instanceof Promise) yield b.then(b=> b ? a : Promise.reject(nop))
// 처리한 값이 false라면 reject를 사용하여 catch로 이동
else if (b) {
console.log(b, "b")
yield a;
}
}
});
Promise값은 true로 구분이 되기 때문에 then을 이용해서 꺼내서 확인을 하고
처리한 값이 false라면 다음에 계산할 함수에 값을 전달하면 안된다.
Promise.reject를 통해 catch로 넘기면 다음 함수를 건너뛸 수 있다!
하지만 reject를 할 경우 실제 에러로 인한 사용인지 값을 전달하지 않기 위한 구분자로써의 사용인지
알 수 없기 때문에 reject에 symbol('nop') 값을 담아서 전달한다.
// take함수 일부
while (!(cur = iter.next()).done) {
const a = cur.value;
if(a instanceof Promise)
return a
.then(a => (res.push(a), res).length === l ? res : recur())
.catch(e => e == nop ? recur() : Promise.reject(e));
// take로 전달된 값이 nop으로 전달되었다면 무시하고 다음 while문을 돌리고
// 실제 에러라면 reject로 에러를 반환
res.push(a);
if (res.length === l) return res;
}
take에서 catch를 사용하여 이어서 처리를 마무리 해주면 된다.
❗❗ 여기서 catch를 다른 함수에 보내서 이어서 처리할 수 있다는 것을 알게 되었다.
결국 then은 reject를 만나면 이후의 then을 무시하고 catch로 넘어가기 때문에
그 원리를 이용한 방식이였던 것이다.
지연평가 + Promise의 효율성
go(
[1,2,3,4,5],
L.map(a => new Promise(resolve => setTimeout(() => resolve(a * a), 1000))),
L.filter(a=>new Promise(resolve => setTimeout(() => resolve(a % 2), 1000))),
take(2),
console.log
)
setTimeout을 사용하여 시간이 소요되는 비동기 로직을 작성 했을 때
전부 평가를 하지 않고 실제 필요한 평가만 진행을 하는 것을 알 수 있었다.
지연된 함수의 병렬 평가 (Concurrency)
위의 지연평가를 보면 실제 필요한 평가만 진행을 하지만 map과 filter가 각각 실행이 되는데
reduce를 사용하여 한번에 동시적으로 처리를 할 수 있다.
const delay500 = a => new Promise(resolve =>
setTimeout(() => resolve(a), 500));
const C = {};
C.reduce = curry((f, acc, iter) => iter ?
reduce(f, acc, [...iter]) :
reduce(f, [...acc]))
// 비동기가 이뤄지는 것을 기다리지 않고 전부 실행
go(
[1,2,3,4,5],
L.map(a => delay500(a * a)),
L.filter(a=>a % 2),
C.reduce((a, b) => a + b),
console.log
)
CallStack에 쌓인 catch 에러 해결
중첩해서 비동기 코드를 실행할 경우 마지막에 평가되는 값은 같지만
catch로 보낸 reject가 처리되지 않아서 console이 지저분해지는데
이 부분은 빈 값의 함수를 통해 해결할 수 있다.
function noop() {}
const catchNoop = arr =>
(arr.forEach(a => a instanceof Promise ? a.catch(noop) : a), arr);
C.reduce = curry((f, acc, iter) => {
return iter ?
reduce(f, acc, catchNoop([...iter])) :
reduce(f, catchNoop([...acc]));
})
* C.take도 동일한 방식으로 작성했다.
+ catch 다루기
var a = Promise.reject('hi');
a = a.catch(a => a);
// a는 catch된 promise이고 이후에 catch를 할 수 없다.
// var a = Promise.reject('hi').catch(a => a); 와 같다.
var a = Promise.reject('hi');
a.catch(a => console.log(a, "catch"));
// a를 담지 않으면 a는 catch가 되지 않은 상태이므로
// 원하는 때에 catch를 할 수 있다.
a.catch(a => console.log(a, "catch")); // hi catch
reject한 promise를 바로 catch할 수 있다.
❗❗ catch후에 다시 catch를 하면 원래 있던 promise를 리턴하기만 한다.
C.filter / C.map
C.take = curry((l, iter) => take(l, catchNoop(iter)));
C.takeAll = C.take(Infinity);
C.map = curry(pipe(L.map, C.takeAll));
C.filter = curry(pipe(L.filter, C.takeAll));
C.map(a => delay500(a * a), [1, 2, 3, 4, 5]).then(console.log);
C.filter(a => delay500(a % 2), [1, 2, 3, 4, 5]).then(console.log);
C.filter와 C.map 또한 위와 같이 만들 수 있고,
즉시, 지연, Promise, 병렬성을 원하는 대로 조합하여 사용할 수 있다.
✍ 느낀 점
then, catch에 대해 조금은 알고 있었지만 이번에 좀 더 활용하는 방법을 배운 것 같다.
또한 한 줄로 코드를 처리하는 방법과 변수 안에서 삼항연산자를 사용하는 것을 알게 되었고
들어오는 값이 어떤 값이냐에 따라 구분해서 처리 함으로써 함수 합성 시 동일한 결과 값을 내거나
같은 결과지만 다른 효율성을 보이는 것들을 보았을 때 코드 자체는 간단하지만 생각을 깊게 하게 만들었다.💡😲
사실 이해하는것 자체에도 벅찼기 때문에 직접 활용하기에는 아직 많이 부족하지만
생각했던 것 보다는 재미있었고 더 알고 싶은 마음이 생겼다!
'스터디' 카테고리의 다른 글
프로그래머스 데브코스 TIL - Day 12 (0) | 2022.04.05 |
---|---|
프로그래머스 데브코스 TIL - Day 11 (0) | 2022.04.04 |
프로그래머스 데브코스 TIL - Day 9 (0) | 2022.04.01 |
프로그래머스 데브코스 TIL - Day 8 (0) | 2022.03.30 |
프로그래머스 데브코스 TIL - Day 7 (0) | 2022.03.29 |