- 비동기 상황
Node.js 로 서버 프로그래밍을 하다보면, 비동기 상황을 자주 만나게 됩니다. DB 혹은 Redis 를 사용하던지 다른 서버 api 를
호출해야 하는 코드에선 비동기 상황을 피할 수 없으며 예전의 경우엔 자연스레 콜백 지옥으로 이어지게 되었습니다.
이 콜백 지옥을 벗어나기 위해 Promise 를 사용하면서 Promise chaining 와 Promise.all 까지 활용해왔습니다.
이번에 볼 예시들은 이러한 비동기 상황에서의 문제 해결 방법으로 partial.js 를 활용해보는 함수형적인 접근입니다. (굳이 partial.js 가 아니어도 됩니다)
const lazyResponse = (result) => {
return new Promise(resolve => {
setTimeout(() => {
resolve(result);
}, 1000);
});
};
단순히 1초 후에 넘겨받은 인자를 그대로 반환하는 함수입니다. 이 함수를 단순 for-loop 상황에서 호출 했을 때 코드를 잠시 보겠습니다.
for (let i = 0 ; i < 10 ; i++) {
lazyResponse(i).then(console.info);
}
// 1초 후 모든 결과 동시 출력
// 0
// 1
// 2
// 3
// ...
// 9
원래 함수의 동작은 1초 후 값을 반환받는 것이므로 처음 0 이 찍히고 1초 후에 1, 다시 또 1초 후에 2 가 찍히는 결과를 기대했습니다만
단순 for-loop 는 그걸 기다리지 않으므로 한번에 0 ~ 9 까지의 값이 넘어간 결과가 찍히게 됩니다.
조금 번거롭긴 하지만, 위의 for-loop 를 조금 수정해도 원하는 결과를 얻을 수 있긴 합니다.
for (let i = 0, p = Promise.resolve() ; i < 10 ; i++) {
p = p.then(() => lazyResponse(i)).then(console.info);
}
// 0 (1초 후)
// 1 (1초 후)
// 2 (1초 후)
// ...
// 9 (1초 후)
위에 작성했던 lazyResponse 함수를 비동기 상황이라고 가정한다면, for-loop 에서 늘 이런식의 코딩은 너무도 번거로울 것 입니다.
이런 상황에서 partial-js 를 활용해보겠습니다.
const _ = require('partial-js');
_.each([1, 2, 3], item => {
console.info(item);
});
// 즉시 결과 출력
// 1
// 2
// 3
_.each([1, 2, 3], item => {
return lazyResponse(item).then(console.info);
});
// 1 (1초 후)
// 2 (1초 후)
// 3 (1초 후)
예전 포스팅에서도 얘기하긴 했었지만, partial-js 의 고차 함수들은 비동기와 동기 상황 모두를 지원합니다.
두번째 인자로 들어온 함수만 다를 뿐 양쪽 상황에서 모두 _.each 를 사용했을 때, 양쪽 모두 기대한 결과를 반환합니다.
즉 partial-js 의 _.each 는 어떤 상황이건 받은 iteratee 의 결과가 나올 때 까지 기다렸다가 다음 iteratee 를 실행하므로,
비동기 상황에서도 동기 상황에서 함수를 선택할 때와 차이 없이 함수를 선택해 제어하면 됩니다.
const select = (id) => lazyResponse({ id: id, c_time: new Date() });
const ids = [1, 2, 3];
_.go(ids,
_.map(id => select(id)),
JSON.stringify,
console.info);
// [{"id":1,"c_time":"2021-12-04T08:15:53.576Z"},
// {"id":2,"c_time":"2021-12-04T08:15:54.581Z"},
// {"id":3,"c_time":"2021-12-04T08:15:55.582Z"}]
위의 select 함수가 DB 에서 조회한 결과를 반환하는 함수라 가정했을 때, _.map 은 select 문이 실행한 후의 결과를 만들 때
유용합니다. 결과에서 보이듯이 select 함수가 1초 간격으로 실행이 된 걸 알 수 있습니다.
_.loop([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], (memo, num, i) => {
return _.go(lazyResponse(memo + num), memo => {
console.info(`${i}번째 ${memo}`);
return memo > 6 ? _.break(memo) : memo;
});
}, 0).then(console.info);
// 0번째 1
// 1번째 3
// 2번째 6
// 3번째 10
// 10
위 코드에서 _.loop 와 _.break 가 하는 짓은 _.reduce 와 비슷하지만 _.loop 를 이용하면 값을 만들어나가면서 _.break 를 이용해
원하는 때에 멈추는게 가능합니다.
partial-js 의 고차 함수들은 비동기 상황을 만나면 내부에서 알아서 로직을 변경해 비동기를 제어하도록 구현되어 있습니다.
개발자가 비동기, 동기 상황을 나누어 처리하지 않아도 아무런 문제가 없다는 얘기이며 순전히 로직을 구현하는데 더 집중할 수 있게 되고,
나아가 개발자 스스로 그 로직을 고차 함수로 만들어 두면 더 많은 비동기 상황을 대응할 수 있다는 것 입니다.
사실 위에 말한 부분들은 요즘의 상황에선 대부분 async - await 로 해결이 가능합니다.
하지만 async - await 로도 promise 의 특징 때문에 동기 함수와의 협업은 불가능 하기도 합니다.
위에서 말한 것 처럼, 동기 상황에서와 비동기 상황을 동일하게 바라볼 수 없고 나눠서 처리해야 한다는 뜻 입니다.
이렇게 얘기하고는 있지만 솔직히 저는 함수형 자바스크립트에서 실질적인 이득은 위의 예시들로 나온 부분 보다는
지연 평가에서 얻을 수 있는 부분 정도라고 생각하긴 합니다. 다시 말해 그냥 일반적인 상황에선 async - await 를 사용하는게
대부분의 개발자들에게는 더 와닿을 거란 말이죠.
하지만 그렇게 생각만 해서는 지연 평가를 적용할 상황에서도 쉽사리 코드가 작업되지는 않습니다. 애초에 함수형으로의 접근이
익숙하지 않기 때문입니다. 따라서 함수형으로 접근해 좋은 함수들을 만들어 두는 작업을 계속 해둠으로써 함수형에 좀 더 가까워지고,
동기 혹은 비동기 상황을 동일하게 바라보는 사고에 익숙해지는 것 부터가 시작이지 않을까 싶습니다.
'Backend > 함수형 자바스크립트' 카테고리의 다른 글
더 많은 함수형 자바스크립트 사용.. (1) (0) | 2021.11.28 |
---|---|
기본 객체 다루기 (0) | 2021.11.13 |
값 다루기 (0) | 2021.11.06 |
지연 평가 (3) (0) | 2021.10.30 |
지연 평가 (2) (0) | 2021.10.23 |