함수의 인자값으로 함수를 받아 해당 함수에 인자값을 전달하면서 로직을 수행하는 방법이다.
function add10(a, callback){
setTimeout(()=>callback(a+10), 100);
}
add10(5, res => {
log(res);
}); // 15
Promise는 인자를 하나만 받는다. 이 함수에서 사용할 인자만 받고 이 함수에서 하는 일이 끝났을 때 사용할 callback 함수는 받지 않고 실제로 연산에 필요한 인자만 받는다.
return
당시에는 Promise타입 객체를 반환하며 .then
메소드를 통해 내부의 값을 꺼내어 사용한다.
function add20(a) {
return new Promise(resolve => setTimeout(() => resolve(a + 20), 100));
}
add20(5)
.then(log)//25
✅ 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
사실상 callback
과 Promise
가장 큰 차이점은 Promise
는 일급값으로 비동기상황을 다룬다는 점이다.
Promise
는 Promise
라는 클래스를 통해서 만들어진 인스턴스를 반환하여 대기,성공,실패를 다루는 일급 값으로 이루어진다. 이는 로직을 끝내는 것을 코드나 컨텍스트로만 다루는게 아니라 대기중이라는 값을 생성한다는 점에서 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
의 경우 비동기로 이뤄진 상황에 대해서 값으로 다룬다는 것이고 일급값이라는 의미가 되며 다른 곳에서 해당 일급값을 재사용할 수 있다는 의미에서 연속성을 가질 수 있다는 점이다.
Promise가 비동기 상황을 값으로 다루는 일급의 성질을 가지고 있다는점을 활용해서 여러가지를 시도할 수 있다.
비동기 상황이 값이라는 것은 함수에게 전달할 수 있고, 해당하는 값이 promise인지 아닌지 등을 확인할 수 있다는 것이다.
✏️ Example
const go1 = (a, f) => f(a);
const add5 = a => a + 5;
log(go1(10, add5)); // 15
go1
이라는 함수가 정상적으로 동작하기 위해서는 인자값(a, f)
들이 모두 동기적으로 값을 알 수 있는 값이어야 한다.
✅ 즉, Promise
객체가 아닐 때만 정상적으로 동작을 한다는 것.
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
는 비동기상황에서 함수 합성을 안전하게 하기 위한 모나드
라고 할 수 있다.
✔️ 함수 합성이란?
f.g = f(g(x))
는 함수 합성이라고 할 수 있다.x
가 g함수
에 적용되고 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 모나드
참조 및 참고하기 좋은 사이트