// callback 패턴
function add10(a, callback) {
setTimeout(() => callback(a + 10), 1000);
}
var a = add10(5, res => {
add10(res, res => {
add10(res, res => {
console.log(res);
});
});
});
console.log(a);
// output : undefined -> 어떤 상황이 일어나는지 알 수 없다. 또한 실행 한 이후 어떠한 일도 할 수 없다.
// 이벤트 관리도 어렵고 눈으로 보기가 힘들다.
// 콜백헬...
// Promise 패턴
function add20(a) { // callback은 따로 받지 않는다. 대신 Promise를 리턴한다.
return new Promise(resolve => setTimeout(() => resolve(a+20), 1000));
}
var b = add20(5)
.then(add20)
.then(add20)
.then(add20)
.then(console.log);
console.log(b);
// output : Promise {<pending>} -> 대기 상태임을 알 수 있다. Promise 객체를 이후에 실행 후에도 다른 일을 할 수 있다.
// 타이핑, 눈으로 정리 쉬움
// 비동기상황을 일급으로 다룬다.
// Promise라는 값은 대기(중요 : 콜백과 다른 점), 성공, 실패를 다루는 일급 값으로 이루어져 있다.
// 일급 : 비동기 상황이 Promise `값`으로서 다루어져 있으므로 일급이다.
const delay100 = a => new Promise(resolve => setTimeout(() => resolve(a), 100));
const go1 = (a, f) => a instanceof Promise ? a.then(f) : f(a);
// promise가 들어오면 promise가 끝나서 a값이 결정 될때까지 기다린다.
const add5 = a => a +5;
const n1 = 10;
console.log(go1(go1(10, add5), console.log));;
const n2 = delay100(10);
console.log(go1(go1(n2, add5), console.log)); // promise
tip)
: typeof는 unary 오퍼레이터이다. unary 오퍼레이터로는 ! 라던가 - 등과 같이 인자를 하나만 받을 수 있는 연산자를 뜻한다. 즉, 함수가 아니고 연산자이기 때문에 괄호를 사용하면 안된다.
: instanceof 는 비교 연산자로 >,<,== 와 같이 두개의 인자를 받는 연산자로 앞의 비교 연산자들을 이용하는 기분으로 사용하면 된다. 하지만 결과로 리턴하는 것은 typeof와는 성질이 조금 다르다. instanceof는 해당하는 변수가 사용하고 있는 prototype의 chain을 2번째 인자와 쭉 비교해서 true/false 값을 리턴한다.
var inst = new Person();
inst instanceof Person; // === true
inst instanceof Object; // === true
typeof inst; // === 'object'
// f * g === f(g(x))
상황에 따라 안전하게 함수를 합성할 수 있도록 하기 위한 도구(javascript에서는 잘 안씀)
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));
// []에 어떤 값이 들어가느냐에 따라서 달라진다.
// [1].map(g).map(f) 까지는 함수합성을 한다.
// .forEach(r => console.log(r)); 효과를 표현한다.
// 이렇게 함수를 합성했을 때 이점이 무엇인가?
[].map(g).map(f).forEach(r => console.log(r));
// output : 아무것도 출력 x
// forEach가 실행되지않아 사용자에게 필요한 효과가 표현 x
// 안전하게 함수를 합성하는 기법이다.
cf) 비동기 상황에서 안전하게 함수를 합성하게 하기 위해서 Promise가 필요하다
Array.of(1).map(g).map(f).forEach(r => console.log(r)); // 4
Promise.resolve(1).then(g).then(f).then(r => log(r));; // 4
Promise.resolve().then(g).then(f).then(r => log(r));; // NaN
// 비동기 상황에서 안전하게 함수를 합성하기 위해 사용한다.
// Promise는 빈값이 들어갔을 때를 위한 안전한 합성을 하는 것이 아니고 대기상황에 대해 안전하게 합성을 하기 위한 도구이다.
new Promise(resolve => setTimeout(() => resolve(2), 100))
.then(g).then(f).then(r => console.log(r));
// 0.1초의 딜레이가 필요한 상황이지만 안전하게 합성할 수 있다.
오류가 있는 상황에서의 함수 합성을 할 수 있는 하나의 규칙이다.
들어오는 인자가 잘못된 인자여서 함수에서 오류가 나거나 정확인 인자가 들어왔더라도 의존하고 있는 외부상황에 따라 오류가 날 수 있는 상황에서 함수를 합성할 수 있다.
수학적인 함수합성
f * g
f(g(x)) = f(g(x))
실무에서의 함수합성
외부요인 혹은 인자에 따라 g에 값이 변화가 가능할 수 있기 때문에
f(g(x)) = f(g(x))가 성립하지 않을 수 있다.
g에서 에러가 날 경우 혹은 f가 제대로 동작하지 않을 경우 아래의 식이 성립할 수 있다.
f(g(x)) = g(x)
var users = [
{ id: 1, name: 'aa' },
{ id: 2, name: 'bb' },
{ id: 3, name: 'cc' },
];
const getUserById = id => find(u => u.id ===id, users);
const f = ({name}) => name;
// name이 들어간 객체를 받았을 경우에만 정상동작
const g = getUserById;
// users가 존재할 때만 정상동작
const fg = id => f(g(id));
console.log(fg(2)); // bb
console.log(fg(2) === fg(2)) // true
// 실 프로그래밍에서는 users가 변할 수 있다.
users.pop();
users.pop();
const r2 = fg(2);
console.log(r2); // Error
// ---------
// 이를 막아주기 위해 어떻게 작성해야 될까?
const getUserById = id => find(u => u.id ===id, users) || Promise.reject('없어요!');
const fg = id => Promise.resove(id).then(g).then(f).catch(a => a);
g(2)
// output : Error! Promise {<reject>} 없어요!
fg(2).then(console.log);
// output : 없어요! (에러아님)
const go = (...args) => reduce((a, f) => f(a), args);
const pipe = (f, ...fs) => (...as) => go(f(...as), ...fs);
// reduce만 수정해주면 go, pipe, 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;
} else {
iter = iter[Symbol.iterator]();
}
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
});
})
go(Promise.resove(1),
a => a + 10,
a => Promise.reject('error~~'),
a => console.log('000'), // 실행 안됨
a => a + 1000,
a => a + 10000,
console.log).catch(a => console.log(a)); // error~~
Promise에서 then으로 꺼낸 것은 Promise가 아니다.
Promise.resolve(Promise.resolve(Promise.resolve(1))).then(console.log);
// 1
// then 으로 꺼낸 것은 Promise가 아니다.
new Promise(resolve => resolve(new Promise(resolve => resolve(1)))).then(console.log);
// 언제든 Promise안에 있는 값을 then으로 한번에 꺼낼 수 있따.