javascript 비동기 동시성 프로그래밍

jodmsoluth·2020년 3월 14일
0

함수형프로그래밍

목록 보기
3/17

callback과 Promise 패턴

// 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 `값`으로서 다루어져 있으므로 일급이다.

값으로서의 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

: typeof는 unary 오퍼레이터이다. unary 오퍼레이터로는 ! 라던가 - 등과 같이 인자를 하나만 받을 수 있는 연산자를 뜻한다. 즉, 함수가 아니고 연산자이기 때문에 괄호를 사용하면 안된다.

  • instanceof

: instanceof 는 비교 연산자로 >,<,== 와 같이 두개의 인자를 받는 연산자로 앞의 비교 연산자들을 이용하는 기분으로 사용하면 된다. 하지만 결과로 리턴하는 것은 typeof와는 성질이 조금 다르다. instanceof는 해당하는 변수가 사용하고 있는 prototype의 chain을 2번째 인자와 쭉 비교해서 true/false 값을 리턴한다.

var inst = new Person();
inst instanceof Person; // === true
inst instanceof Object; // === true
typeof inst; // === 'object'

함수 합성 과정에서의 Promise

함수합성

// 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초의 딜레이가 필요한 상황이지만 안전하게 합성할 수 있다.

Kleisli Composition 관점에서의 Promise

오류가 있는 상황에서의 함수 합성을 할 수 있는 하나의 규칙이다.
들어오는 인자가 잘못된 인자여서 함수에서 오류가 나거나 정확인 인자가 들어왔더라도 의존하고 있는 외부상황에 따라 오류가 날 수 있는 상황에서 함수를 합성할 수 있다.

  • 수학적인 함수합성
    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 : 없어요! (에러아님)

go, pipe, reduce에서 비동기 제어

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에서 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으로 한번에 꺼낼 수 있따.
            
profile
풀스택이 되고 싶은 주니어 웹 개발자입니다.

0개의 댓글