해당 강의를 듣고 정리한 글입니다.... 🐿️
https://www.inflearn.com/course/functional-es6
callback 함수와 Promise로 작성한 예시이다. callback은 Promise에 비해 가독성이 떨어지는 것을 볼 수 있다.
function add10(a, callback) {
setTimeout(() => callback(a + 10), 100);
}
var a = add10(5, res => {
add10(res, res => {
add10(res, res => {
// console.log(res);
});
});
});
function add20(a) {
return new Promise(resolve => setTimeout(() => resolve(a + 20), 100));
}
var b = add20(5)
.then(add20)
.then(add20)
사실 Promise가 callback과 다르게 특별한 차이점은 then을 사용해 결과를 꺼내는게 아니라 비동기 상황을 일급 값으로 다룬다는 것이다.
function add10(a, callback) {
setTimeout(() => callback(a + 10), 100);
}
var a = add10(5, res => {
add10(res, res => {
add10(res, res => {
// consolg.log(res);
});
});
});
console.log(a); // undefined
function add20(a) {
return new Promise(resolve => setTimeout(() => resolve(a + 20), 100));
}
var b = add20(5)
.then(add20)
.then(add20)
console.log(b); // Promise
a는 undefined
, b는 Promise
가 리턴된다.
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);
const add5 = a => a + 5;
const n1 = 10;
go1(go1(n1, add5), console.log); // 15
const n2 = delay100(10);
go1(go1(n2, add5), console.log); // 15
모나드란 함수 합성을 안전하게 하기 위한 도구이다.
아래의 함수는 정상적으로 동작하여 4
를 출력한다.
const g = a => a + 1;
const f = a => a * a;
console.log(f(g(1))); // 4
하지만, console.log(f(g()));
를 실행한다고 가정해보면 NaN
이 출력되는데, 이 함수는 안전하게 함수 합성이 되지않았다.
어떻게 하면 안전하게 합성을 할 수 있을까?에 대한 아이디어가 모나드이다.
[]
가 모나드이다. map
라는 함수로 함수를 합성한다.
[1].map(g).map(f).forEach(a => console.log(a)); // 4
[].map(g).map(f).forEach(a => console.log(a)); // 아무것도 출력되지 않음
이전의 console.log(f(g()));
형태의 함수 합성은 NaN
이 출력되어 안전하게 함수 합성을 하지 못하였는데, 모나드 형태의 []
로 map을 통해 함수를 합성했을 때는 아무것도 출력되지 않는 (효과를 일으키지 않는) 식으로 동작한다.
Promise
는 then
이라는 함수로 함수를 합성한다. Promise
는값의 존재 여부의 상황에서 안전하게 쓰이는 것이 아니라 비동기 상황에서 안전하게 쓰인다고 볼 수 있다.
Promise.resolve(2).then(g).then(f).then(r => console.log(r)); // 4
Promise.resolve().then(g).then(f).then(r => console.log(r)); // NaN
Kleisli Composition은 오류가 있을 수 있는 상황에서의 함수 합성을 안전하게 할 수 있는 하나의 규칙이다.
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;
const g = getUserById;
const fg = id => f(g(id));
console.log(fg(2)); // bb
하지만 실제 동작하는 어플리케이션에서는 user가 변하는 경우가 흔하다.
users.pop();
users.pop();
users.pop()
된 이후에는 console.log(fg(2));
를 실행하면 에러가 난다.
users
가 변경된 이후에도 에러가 나지 않도록 하는 것이 Kleisli Composition이다.
아래의 코드 처럼 작성하면 f(g(x))
합성된 함수에서, g(x)
에러가 나면 f(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) || Promise.reject('없어요!');
const f = ({name}) => name;
const g = getUserById;
const fg = id => Promise.resolve(id).then(g).then(f).catch(a => a);
fg(2).then(console.log); // 없어요!
아무리 Promise
가 중첩되더라도 한번에 then
으로 꺼낼 수 있다.!
Promise.resolve(Promise.resolve(1)).then(function (a) {
log(a);
});
new Promise(resolve => resolve(new Promise(resolve => resolve(1)))).then(log);