JavaScript - 비동기:동시성 프로그래밍(1)

박정호·2022년 11월 3일
0

JS

목록 보기
22/24
post-thumbnail

⭐️ Callback & Promise

Callback

함수의 인자값으로 함수를 받아 해당 함수에 인자값을 전달하면서 로직을 수행하는 방법이다.

function add10(a, callback){
	setTimeout(()=>callback(a+10), 100);
}
add10(5, res => {
	log(res);
}); // 15

Promise

Promise는 인자를 하나만 받는다. 이 함수에서 사용할 인자만 받고 이 함수에서 하는 일이 끝났을 때 사용할 callback 함수는 받지 않고 실제로 연산에 필요한 인자만 받는다.

return 당시에는 Promise타입 객체를 반환하며 .then 메소드를 통해 내부의 값을 꺼내어 사용한다.

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

add20(5)
    .then(log)//25

Callback vs Promise

Callback은 함수를 인자로 받아 수행하는 반면, Promise는 Promise객체를 반환한다. 즉, 로직이 수행되는 주체가 다르다.

✅ Code Depth가 Callback의 경우 계속해서 증가하는 반면 Promise 의 경우 1 Depth에서 더이상 깊어지지 않는다.

//callback
add10(5, res=>{
    add10(res, res=>{//1_depth
        add10(res, res=>{//2_depth
            add10(res, res=>{//3_depth
                log(res);
            })
        })
    })
});

//Promise
add20(5)
    .then(add20)//1_depth
    .then(add20)//1_depth
    .then(add20)//1_depth
    .then(add20)//1_depth
    .then(log);//1_depth

⭐️ 비동기 값으로 만드는 Promise

사실상 callbackPromise 가장 큰 차이점은 Promise는 일급값으로 비동기상황을 다룬다는 점이다.

PromisePromise라는 클래스를 통해서 만들어진 인스턴스를 반환하여 대기,성공,실패를 다루는 일급 값으로 이루어진다. 이는 로직을 끝내는 것을 코드나 컨텍스트로만 다루는게 아니라 대기중이라는 값을 생성한다는 점에서 Callback과 가장 큰 차이점이다.

callback비동기적인 상황을 코드로만 표현되고 있는 반면, promise 같은 경우에는 비동기적인 상황에 대한 값을 만들어서 return을 하고 있다는 점이 큰 차이점이자 중요한 차이점이다.


callback함수(add10), promise함수(add20)를 각각 변수 a, b에 담아서 변수를 출력해보자.
callback의 결과를 담은 a와 다르게 Promise의 반환객체를 담은 b의 출력값을 보면 Promise 객체를 보여준다.

✅ 이는 callback은 반환값에 중점을 두는게아닌 코드적인 상황(setTimeout)이나 Context(callback)만 중점으로 둔다는 것이다.

Promise는 즉시 Promise객체를 반환된다는 특징이 있는데, 이는 callback과는 다르게 Context에 함수의 로직에 이어지는 로직을 then을 통해 추가적으로 이어갈 수 있다는 것이다.

✅ 즉, Promise의 경우 비동기로 이뤄진 상황에 대해서 값으로 다룬다는 것이고 일급값이라는 의미가 되며 다른 곳에서 해당 일급값을 재사용할 수 있다는 의미에서 연속성을 가질 수 있다는 점이다.

  • then을 통한 연속성

⭐️ 값으로서의 Promise활용

Promise가 비동기 상황을 값으로 다루는 일급의 성질을 가지고 있다는점을 활용해서 여러가지를 시도할 수 있다.

비동기 상황이 값이라는 것은 함수에게 전달할 수 있고, 해당하는 값이 promise인지 아닌지 등을 확인할 수 있다는 것이다.

✏️ Example

const go1 = (a, f) => f(a);
const add5 = a => a + 5;
log(go1(10, add5)); // 15

go1이라는 함수가 정상적으로 동작하기 위해서는 인자값(a, f)들이 모두 동기적으로 값을 알 수 있는 값이어야 한다.

즉, Promise객체가 아닐 때만 정상적으로 동작을 한다는 것.



만약 `a`라는 인자값이 비동기적으로 10초 후에 나타나는 값이라면 어떻게 동작할까?
const delay100 = a => new Promise(resolve => setTimeout(()=>resolve(a),100));
log(go1(delay100(10), add5));//[object Promise]5

위와 같은 코드 또한 정상 작동이 되지 않는다.

그럼 Promise객체를 일급 값으로써 받는 경우에도 정상적으로 동작하게 하기 위해서는 어떻게 구현할까?

  • go1 함수에서 a인자값이 Promise인지 평가해서 상황에 맞는 로직(a.then or f(a))을 수행.
  • 결과값이 나온 후에 출력을하면 Promise 객체가 출력.
    (해당 일급값을 아직 이어서 추가적인 작업을 지속적으로 수행할 수 있다는 의미)
const go1 = (a, f) =>  a instanceof Promise ? a.then(f): f(a);
var r2 = go1(delay100(10), add5);
r2.then(log);//15

go1(go1(delay100(10), add5), log);//15

const n2 = delay100(20);
go1(go1(n2, add5), log);//25
log(go1(go1(n2, add5), log));//Promise {<pending>}


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

함수형 프로그래밍이나 함수합성에서 모나드에 개념을 알고 있으면 좀 더 높은 퀄리티의 코드를 작성할 수 있다.

Promise는 비동기상황에서 함수 합성을 안전하게 하기 위한 모나드라고 할 수 있다.

  • x라는 인자값을 a라는 함수에게 전달했을때 a라는 함수가 수행 된 이후 그 값이 바로 b라는 함수에 전달되는 것을 함수 합성이라 하는데, 이러한 함수 합성을 안전하게 수행하도록 하는 것을 모나드라 하는데 대표적으로 Promise가 있다. ⇒ b(a(x))

✔️ 함수 합성이란?

  • f.g = f(g(x))함수 합성이라고 할 수 있다.
    즉, 인자 xg함수에 적용되고 g함수의 실행결과를 f함수에 전달하여 f함수가 실행되고 결과를 만드는 과정을 말한다.

✔️ 모나드란?

  • 모나드는 일종의 박스이고 박스안에 값이 들어있다고 볼 수 있다. (ex: [1])
    그리고 이 값을 통해서 함수합성들을 안전하게 수행해나가는 것이라 볼 수 있다.


모나드의 사용이유

다음은 함수합성을 통해서 값을 출력하는 것이다.

const g = a => a+1;
const f = a => a*a;

log(f(g(1))); //4

위와 같은 경우는 정상적으로 작동하겠지만, 아래와 같이 인자값이 없는 상태로 함수합성이 이루어지면 어떻게 될까?

log(f(g())); //NaN

값이 호출되지 않거나, 에러가 발생할 수 있는데, 실제로도 어떤 값이 올지 모르는 상황이 있을 수 있다.

즉, 위와 같은 함수합성은 안전하지 못한 함수합성이며 이와 같은 불안한 상황에서 안전하게 함수합성을 하기 위해서 나온 것이 모나드이다.
모나드는 박스를 가지고 있고 박스 내부에 실제 효과나 연산에 필요한 재료들을 가지고 있으며 이를 통해 함수합성을 한다.

log([1].map(g).map(f)); // [4]

반환값이 Array 타입인걸 볼 수 있는데 이는 필요한 값은 아니라, Array라는 값은 개발자가 값을 다룰 때 사용하는 도구이다. 즉, 최종 유효값이 아니라 Array안에 있는 최종 값을 꺼내는 것이다.

따라서, forEach를 통해서 Array타입인 [4]를 실제 값으로 뽑아내준다.

[1].map(g).map(f).forEach(r=>log(r)); //4

log(f(g(1)))와 같은 뜻이지만, 유효하지 않거나 없는 값에 대해서 에러가 발생하지 않는다. 최종 결과값을 도출할 때까지 안전하게 종료된다.

[].map(g).map(f).forEach(r=>log(r)); // 결과 없음

👍 아래와 같이 result1, result2라는 값이 중간에 필요할까? 실제 값인 result3 가 필요한 것이지 결과까지 가는 중간 과정인 Array 값이 필요한 것이 아니라는 것이다.

const g = a => a+1;
const f = a => a*a;
let arr = [1,2,3];

let result1 = arr.map(g); // [2,3,4];
let result2 = result.map(f); // [4,9,16];
let result3 = result2.forEach(r => log(r)); // 4, 9, 16

참고: JavaScript 모나드


참조 및 참고하기 좋은 사이트

profile
기록하여 기억하고, 계획하여 실천하자. will be a FE developer (HOME버튼을 클릭하여 Notion으로 놀러오세요!)

0개의 댓글