함수형 프로그래밍 with JS ⑤

:D ·2023년 5월 16일
0

해당 강의를 듣고 정리한 글입니다.... 🐿️
https://www.inflearn.com/course/functional-es6

섹션 8. 비동기:동시성 프로그래밍 1

callback과 Promise

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

사실 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의 결과를 값으로 만들어 지속적으로 함수들을 연결해 나갈 수 있는 것이 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

합성 관점에서의 Promise와 모나드

모나드란 함수 합성을 안전하게 하기 위한 도구이다.
아래의 함수는 정상적으로 동작하여 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을 통해 함수를 합성했을 때는 아무것도 출력되지 않는 (효과를 일으키지 않는) 식으로 동작한다.

Promisethen이라는 함수로 함수를 합성한다. 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 관점에서의 Promise

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가 중첩되더라도 한번에 then 으로 꺼낼 수 있다.!

Promise.resolve(Promise.resolve(1)).then(function (a) {
  log(a);
});

new Promise(resolve => resolve(new Promise(resolve => resolve(1)))).then(log);
profile
강지영입니...🐿️

0개의 댓글