파트9. 비동기 동시성 프로그래밍(1) - ChoDragon9/posts GitHub Wiki

Callback과 Promise의 차이

Promise는 비동기를 값으로 다룰 수 있다는 것이 큰 차이이다. 그래서 콜백으로 처리하는 함수는 리턴되는 값이 없지만 Promise로 처리하는 함수는 리턴된 Promise를 통해서 연속적인 동작을 할 수 있다.

const add10 = (a, callback) => {
  setTimeout(() => callback(a + 10), 100);
};

add10(10, res => {
  add10(res, res => {
    console.log(res);
  });
});

const add20 = (a, callback) => {
  return new Promise(resolve => setTimeout(() => resolve(a + 20), 100));
};

add20(10)
  .then(add20)
  .then(console.log);

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

모나드는 함수합성을 안전하게 하기 위한 컨테이너입니다. 비정상적인 값이 인자로 들어올 때 비정상적인 효과를 만들지 않기 위해 사용합니다.

// (f . g)(x) = f(g(x))
const g = a => a + 1;
const f = a => a * a;

f(g(1)); // 정상적인값
f(g()); // 비정상적인값

[1].map(g).map(f);
[].map(g).map(f);

Promise는 비동기 상황일 때 안전한 합성을 하게 합니다. Array와 비슷한 방법으로 기술이 가능합니다.

Array.of(1).map(g).map(f).forEach(r => console.log(r));

Promise.resolve(2).then(g).then(f).then(r => console.log(r))

Promise는 비동기 상황의 안전한 합성을 하게되는 데, 합성하는 시점을 안전하게 하는 용도의 안전한 합성을 하게 됩니다. 데이터의 문제가 있을 때 상황에는 용도가 맞지 않고 비정상적인 값이 왔을 때는 비정상적으로 동작됩니다.

Promise.resolve()
  .then(g)
  .then(f)
  .then(r => console.log(r)) // NaN

Kleisli Composition

함수 합성시 오류가 발생하는 상황일 때 안전한 합성을 처리하는 방법중 하나. 함수나 인자에 문제가 발생했을 때 f(g(x)) = g(x)가 성립되도록 구현하는 것입니다.

// f . g
// f(g(x)) = f(g(x))
// f(g(x)) = g(x)

const users = [
  {id: 1, name: 'aa'},
  {id: 2, name: 'bb'},
  {id: 3, name: 'cc'}
];

const getUserById = id =>
  users.find(u => u.id == id) || Promise.reject('없어요!');
const f = ({name}) => name;
const g = getUserById;

const fg = id => Promise.resolve(id).then(g).then(f);

fg(3).then(console.log);
g(3);

비동기 제어

reduce에 Promise를 사용가능하도록 하면 안정적인 함수 합성을 할 수 있습니다.

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.resolve(1),
  a => a + 10,
  a => Promise.resolve(a + 100),
  a => a + 1000,
  console.log
)

go(Promise.resolve(1),
  a => a + 10,
  a => Promise.reject('error'),
  a => console.log('---'),
  a => a + 1000,
  console.log
).catch(a => console.log(a))

promise.then의 중요한 규칙

Promise.resolve(Promise.resolve(Promise.resolve(1))).then(console.log);