[JavaScript ES6+] 비동기: 동시성 프로그래밍

Yujin Lee·2021년 5월 13일
0

JavaScript

목록 보기
14/14
post-thumbnail

1. 동기 vs. 비동기?

동기(synchronous : 동시에 일어나는)

  • 요청과 그 결과가 동시에 일어난다.
  • 요청을 하면 시간이 얼마가 걸리던지 요청한 자리에서 결과가 주어져야 한다.
  • A노드와 B노드 사이의 작업 처리 단위(transaction)를 동시에 맞춘다는 것


비동기(Asynchronous : 동시에 일어나지 않는)

  • 요청과 결과가 동시에 일어나지 않는다.
  • 노드 사이의 작업 처리 단위를 동시에 맞추지 않아도 된다.

비동기방식은 동기보다 복잡하지만 결과가 주어지는데 시간이 걸리더라도 그 시간 동안 다른 작업을 할 수 있으므로 자원을 효율적으로 사용할 수 있는 장점이 있다.

동기는 추구하는 같은 행위(목적)가 동시에 이루어지며, 비동기는 추구하는 행위(목적)가 다를 수도 있고, 동시에 이루어지지도 않는다.


(출처: https://private.tistory.com/24)




2. callback과 Promise

<script>
    function add10(a, callback) {
        //인자를 받고(a), 함수가 일을 마쳤을 떄 결과를 전달할(callback)
        setTimeout(() => callback(a + 10), 100); //100ms 딜레이
    }

    var a = add10(5, res => {
        add10(res, res => {
            add10(res, res => {
                log(res);
            });
        });
    });
</script>

똑같은 작업을 Promise로 재구현

<script>
  function add20(a) {
      return new Promise(resolve => setTimeout(() => resolve(a + 20), 100));
    }

  var b = add20(5)
      .then(add20)
      .then(add20)
      .then(log);
</script>




3. Promise

1) 비동기를 값으로 만드는 Promise

callback과 가장 큰 차이점

promise는 promise라는 클래스를 통해 만들어진 인스턴스를 반환하는데
대기, 성공, 실패를 다루는 일급값으로 이루어져 있다.

방금 위에서 만든 a와 b를 콘솔에 찍어보자.

log(a);
log(b);

callback으로 생성된 변수는 undefined되나 / promise는 즉시 리턴된다.

그래서 Promise의 경우, 어떤 함수에게 전달될 수도 있고 쭉 이벤트를 이어나갈 수 있다는 게 가장 중요한 차이점이다.



2) 값으로서의 Promise 활용

<script>
    const delay100 = a => new Promise(resolve =>
        setTimeout(() => resolve(a), 100));

    const go_promise = (a, f) => a instanceof Promise ? a.then(f) : f(a);
    const add5 = a => a + 5;

    var r = go_promise(10, add5);
    log(r);

    var r2 = go_promise(delay100(10), add5);
    r2.then(log);
</script>

이는 아래의 식과 완전히 동일한 표현이다.

    const n1 = 10;
    go_promise(go_promise(n1, add5), log);

    const n2 = delay100(10);
    go_promise(go_promise(n2, add5), log);

근데 n1은 Promise가 아니라서 이 자체를 log찍어보면 undefined가 나오지만 n2는 Promise가 나온다.

    const n1 = 10;
    log(go_promise(go_promise(n1, add5), log));

    const n2 = delay100(10);
    log(go_promise(go_promise(n2, add5), log));

Promise를 사용하면 어떤 일을 한 결과의 상황을 일급값으로 만들어서 지속적으로 어떤 일들을 연결해 나갈 수 있다!



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

f · g 또는 f(g(x))함수 합성
함수를 상황에 따라 안전하게 합성할 수 있게 하기 위해 모나드라는 개념이 있고
비동기 상황을 안전하게 합성하는 것이 Promise

가령 이런 코드가 있을 때

<script type="module">
    const g = a => a + 1;
    const f = a => a * a;

    log(f(g(1))); // 1 -> 4
    log(f(g())); // 공백 -> ?
  ...
</script>

두 번째 로그는 공백에서 에러가 뜨기 때문에 안전한 합성이라고 보기 어렵다. 안전한 인자가 들어와야 하는 함수라고 볼 수 있다. 값이 있는지 없는지 모를 때에도 안전하게 합성하려면? 모나드!

<script type="module">
    const g = a => a + 1;
    const f = a => a * a;
  
    Array.of(1).map(g).map(f).forEach(r => log(r));
    [].map(g).map(f).forEach(r => log(r));

    Promise.resolve(2).then(g).then(f).then(r => log(r));
    new Promise(resolve =>
        setTimeout(() => resolve(2), 100)
    ).then(g).then(f).then(r => log(r));
</script>


이렇게 했을 떄 장점

원래는 빈 값이 들어와도 강제로 함수가 실행되니까 에러가 발생했으나

모나드 형태의 Array로 map을 통해서 함수를 합성하니까 안전하게 함수가 합성된다.



4) Kleisli Composition 관점에서의 Promise

오류가 있을 수 있는 상황에서 안전하게 함수합성하는 규칙 중 하나

<script type="module">
    // f . g
    // f(g(x)) = f(g(x))
    // f(g(x)) = g(x)

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

    //user를 id값으로 찾는 함수를 만들자

    const getUserById = id =>
        find(u => u.id == id, users);

    const f = ({name}) => name;
    const g = getUserById;
    const fg = id => f(g(id));
  
</script>

이 경우 users가 pop push되서 막~ 변할 때 대응하기 어렵다.
예를 들어 두 번의 pop으로 데이터가 id: 1일 때만 남아있는데 fg(2)를 부르면 에러가 발생한다.

Promise

<script type="module">
  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('???: 없어요!\n???: 아 있었는데?\n???: 아뇨 없어요.');
  const f = ({name}) => name;
  const g = getUserById;
  
  const fg = id => Promise.resolve(id).then(g).then(f).catch(a => a);

  fg(2).then(log);
  
  users.pop();
  users.pop();

  fg(1).then(log);
  
  setTimeout(function () {
        users.pop();
        users.pop();
        fg(2).then(log);
    }, 10);
</script>




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

<script>
go(1,
  a => a + 10,
  a => a + 1000,
  a => a + 10000,
  log);
</script>


fx.js파일의 reduce를 다음과 같이 변경한다.

const reduce = curry((f, acc, iter) => {
    if (!iter) {
        iter = acc[Symbol.iterator]();
        acc = iter.next().value;
    } else {
        iter = iter[Symbol.iterator]();
    }
    let cur;
    while (!(cur = iter.next()).done) {
        const a = cur.value;
        acc = f(acc, a);
    }
    return acc;
});

👇🏻

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;
    });
});

그리고 이 코드를 추가한다.

const go1 = (a, f) => a instanceof Promise ? a.then(f) : f(a);


응용 1

<script>
    go(Promise.resolve(1),
        a => a + 10,
        a => Promise.resolve(a + 100),
        a => a + 1000,
        a => a + 10000,
        log).catch(a => console.log(a));
</script>


응용 2

<script>
    go(Promise.resolve(1),
        a => a + 10,
        a => Promise.resolve(a + 100),
        a => Promise.reject('error !'), //여기서 stop
        a => console.log('----'), //출력되지 않는다
        a => a + 1000,
        a => a + 10000,
        log).catch(a => console.log(a));
</script>

profile
I can be your Genie🧞‍♀️ How ‘bout Aladdin? 🧞‍♂️

0개의 댓글