#1 take, takeWhile, takeUntil로 코드 다루기
takeWhile, takeUntil
먼저 range와 take를 다시 알아보자.
위 두 개는 즉시 평가되는 것과 지연 평가되는 것으로 나뉘는데 이를 해석해보면 다음과 같다.
_.go(
_.range(10), /* 0 ~ 9 까지의 배열 */
_.take(3), /* 앞에서부터 3개만 자르기 */
_.each(console.log)); // 0 1 2
_.go(
L.range(10), /* 0 ~ 9 까지의 이터러블, 최대 10번 수행 */
L.take(3), /* 최대 3개의 값이 필요하고, 최대 3번의 일을 수행 */
_.each(console.log)); // 0 1 2
위 항목에서 range이후에 delay(1000)이 들어간다면, 즉시 평가의 경우 10초 정도 걸릴 것이며, 지연 평가의 경우엔 L.take에서 단 3개의 값 만들 필요하기 때문에, 3초 정도가 걸릴 것이다.
이 챕터에선 이와 같은 시간적인 측면을 다룰 것이며, 이어서 takeWhile과 takeUntil을 알아보자. takeWhile이나 takeUntil은 좀 더 동적으로 일어나는 일을 효과적으로 제한하면서 다룰 수 있다.
takeWhile은 받아 오는 값이 true일 때까지만 받는 것이다.
/* false값을 만나면 더이상 받지않음 */
_.go(
[1, 2, 3, 4, 5, 6, 7, 8, 0, 0, 0],
_.takeWhile(a => a),
_.each(console.log)); // 1 2 3 4 5 6 7 8
/* 이후에 true값을 만나도 false전에 종료 */
_.go(
[1, 2, 3, 4, 5, 6, 7, 8, 0, 1, 2],
_.takeWhile(a => a),
_.each(console.log)); // 1 2 3 4 5 6 7 8
takeUntil은 반대로 받아오는 값이 처음 만족하는 값일 때까지 받고 종료된다.
_.go(
[1, 2, 3, 4, 5, 6, 7, 8, 0, 0, 0],
_.takeUntil(a => a),
_.each(console.log)); // 1
_.go(
[0, false, undefined, null, 10, 20, 30],
_.takeUntil(a => a),
_.each(console.log)); // 0 false undefined null 10
할 일들을 이터러블(리스트)로 바라보기
const track = [
{ cars: ['철수', '영희', '철희', '영수'] },
{ cars: ['하든', '커리', '듀란트', '탐슨'] },
{ cars: ['폴', '어빙', '릴라드', '맥컬럼'] },
{ cars: ['스파이더맨', '아이언맨'] },
{ cars: [] }
];
위 코드를 통해 만들어 볼 것은 자동차 경주이다. "트랙 안에 각 조가 있으며, 각 조에 해당하는 사람들끼리 경쟁을 하는 것이고, 4명이 다 찬 조만 출발시키는다는 스케줄러"이다.
만약, 4조같은 경우엔 4명이 다 차지 않아서 출발을 시키지 않는다. 또 1조를 출발시킨 이후에 딜레이를 주고 2조를 출발시킨다.
위 내용을 통해 코드를 짜 보면 다음과 같이 나온다.
_.go(
L.range(Infinity),
L.map(i => track[i]), /* 각 조를 꺼냄 */
L.map(({cars}) => cars),
L.map(_.delay(2000)), /* 2초 딜레이 */
L.takeWhile(({length: l}) => l == 4), /* 4명인 조만 출발시킴 */
L.flat, /* 배열에서 값을 꺼내줌 */
L.map(car => `${car} 출발!`), /* 출발 문구 */
_.each(console.log));
/*
(2초딜레이)
철수 출발!
영희 출발!
철희 출발!
영수 출발!
(2초딜레이)
하든 출발!
커리 출발!
듀란트 출발!
탐슨 출발!
(2초딜레이)
폴 출발!
어빙 출발!
릴라드 출발!
맥컬럼 출발!
*/
만약 위코드에서, 4명이 다 찬 조가 아닌 4조까지만 출발을 시키려고 할 때는 takeUntil을 사용해서 나타내면 될 것이다.
_.go(
L.range(Infinity),
L.map(i => track[i]), /* 각 조를 꺼냄 */
L.map(({cars}) => cars),
L.map(_.delay(2000)), /* 2초 딜레이 */
L.takeWhile(({length: l}) => l == 4), /* 4명이 아닌 조까지만 출발시킴 */
L.flat, /* 배열에서 값을 꺼내줌 */
L.map(car => `${car} 출발!`), /* 출발 문구 */
_.each(console.log));
/*
(2초딜레이)
철수 출발!
영희 출발!
철희 출발!
영수 출발!
(2초딜레이)
하든 출발!
커리 출발!
듀란트 출발!
탐슨 출발!
(2초딜레이)
폴 출발!
어빙 출발!
릴라드 출발!
맥컬럼 출발!
(2초딜레이)
스파이더맨 출발!
아이언맨 출발!
*/
#2 아임포트 결제 누락 처리 스케줄러
결제된 내역 가져오기
이터러블 프로그래밍으로 좀 더 실무적인 코드 중 결제 모듈을 붙이는 아임포트 서비스를 다뤄 볼 것이다. 그중 결제 누락이 된 후 처리하는 스케줄러를 볼 것이다.
const Impt = {
payments: {
1: [
{ imp_id: 11, order_id: 1, amount: 15000 },
{ imp_id: 12, order_id: 2, amount: 25000 },
{ imp_id: 13, order_id: 3, amount: 10000 }
],
2: [
{ imp_id: 14, order_id: 4, amount: 25000 },
{ imp_id: 15, order_id: 5, amount: 45000 },
{ imp_id: 16, order_id: 6, amount: 15000 }
],
3: [
{ imp_id: 17, order_id: 7, amount: 20000 },
{ imp_id: 18, order_id: 8, amount: 30000 }
],
4: [],
5: [],
//...
},
getPayments: page => {
console.log(`http://..?page=${page}`);
return _.delay(1000 * 1, Impt.payments[page]);
},
cancelPayment: imp_id => Promise.resolve(`${imp_id}: 취소완료`)
};
const DB = {
getOrders: ids => _.delay(100, [
{ id: 1 },
{ id: 3 },
{ id: 7 }
])
};
위 코드는 가상으로 작성된 코드이며, 최대 1page당 3개까지 값을 가져올 수 있으며, 3개보다 값이 적거나 없으면 더 이상 진행을 안 하는 식으로 동작이 된다.
imp_id는 결제 모듈 측 id이며, order_id는 가맹점에서 만들었던 id, amount는 가격이다.
getPayments는 해당하는 페이지에 요청을 하고 값을 꺼내오는 것이다.
Impt.getPayments(2).then(console.log)
/*
http://..?page=2
Promise {<pending>}
(딜레이 2초)
▼(3) [{…}, {…}, {…}]
▶0: {imp_id: 14, order_id: 4, amount: 25000}
▶1: {imp_id: 15, order_id: 5, amount: 45000}
▶2: {imp_id: 16, order_id: 6, amount: 15000}
length: 3
▶__proto__: Array(0)
*/
cancelPayment는 승인된 결제내역을 취소하는 것으로 imp_id를 지정해 취소하면 실제론 사용자에게 문자도 가고, 신용카드도 취소도 되는 식으로 동작되는 식으로 동작이 된다.
Impt.cancelPayment(17) // Promise {<resolved>: "17: 취소완료"}
그리고 DB는 가맹점에서 사용하고 있는 DB모듈이다.
이제 결제된 결제 모듈 측 payments를 가져와서, 하나로 합쳐보자.
async function job(){
const payments = _.go(
L.range(1, Infinity), /* 언제까지 값을 꺼낼지 모르기때문에 Infinity */
L.map(Impt.getPayments),
L.takeUntil(({length}) => length < 3),
_.each(console.log);
}
/*
http://..?page=1
▶(3) [{…}, {…}, {…}]
http://..?page=2
▶(3) [{…}, {…}, {…}]
http://..?page=3
▶(2) [{…}, {…}]
*/
위와 같이 나타낸다면, 결제 데이터가 있을 때마다 계속 출력하므로 한 번에 출력하기 위해선 아래처럼 해줘야 한다.
async function job(){
// 결제된 결제모듈측 payments 가져온다.
// page 단위로 가져오는데,
// 결제 데이터가 있을 때까지 모두 가져와서 하나로 합친다.
const payments = await _.go(
L.range(1, Infinity),
L.map(Impt.getPayments),
L.takeUntil(({length}) => length < 3),
_.flat);
console.log(payments);
}
job();
/*
http://..?page=1
http://..?page=2
http://..?page=3
▼(8) [{…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}]
▶0: {imp_id: 11, order_id: 1, amount: 15000}
▶1: {imp_id: 12, order_id: 2, amount: 25000}
▶2: {imp_id: 13, order_id: 3, amount: 10000}
▶3: {imp_id: 14, order_id: 4, amount: 25000}
▶4: {imp_id: 15, order_id: 5, amount: 45000}
▶5: {imp_id: 16, order_id: 6, amount: 15000}
▶6: {imp_id: 17, order_id: 7, amount: 20000}
▶7: {imp_id: 18, order_id: 8, amount: 30000}
length: 8
▶__proto__: Array(0)
*/
가맹점의 DB의 주문서 가져오기
이번엔 가맹점 측에 있는 결제가 성공된 id값을 뽑아 볼 것이다.
async function job(){
...
// 결제가 실제로 완료된 가맹점 측 주문서 id들을 뽑는다.
const order_ids = await _.go(
payments,
_.map(p => p.order_id), /* order_id 뽑기 */
DB.getOrders, /* 실제로 결제된 id 반환 */
_.map(({id}) => id)); /* id 뽑아내기*/
console.log(order_ids);
}
job();
/*
http://..?page=1
http://..?page=2
http://..?page=3
(3) [1, 3, 7]
*/
비교 후 결제 취소 API 실행하기
payments와 order_Ids를 비교해서, 실제로 결제가 완료된 것만 남기고, 가맹점 측 id에서 알 수 없는 것들은 취소 API를 통해 취소를 하려고 한다. 즉, payments 중에 정말 결제가 실제로 된 것들을 걸러주게끔 하면 된다.
async function job(){
...
// 결제모듈의 payments와 가맹점의 주문서를 비교해서
// 결제를 취소해야할 id들을 뽑아서
// 결제 취소 api를 실행
await _.go(
payments,
L.reject(p => order_ids.includes(p.order_id)),
L.map(p => p.imp_id),
L.map(Impt.cancelPayment),
_.each(console.log));
}
/*
http://..?page=1
http://..?page=2
http://..?page=3
12: 취소완료
14: 취소완료
15: 취소완료
16: 취소완료
18: 취소완료
*/
스케줄러 반복 실행하기
스케줄러를 실행할 때 반복 실행이 되게 할 때 아래처럼 짤 수가 있다.
(function recur(){
job().then(recur);
})();
하지만, 위와 같이 짠다면 너무 부하가 많이 걸릴 것이어서, delay를 통해 실행시간을 조정해준다.
// 7초에 한 번만 한다.
// 그런데 만일 job 7초보다 더 걸리면, job이 끝날 때까지
(function recur() {
Promise.all([
_.delay(7000, undefined),
job()
]).then(recur);
}) ();
이렇게 짜면, job 작업이 7초 전에 끝나면 7초가 될 때까지 기다렸다가, 반복 실행이 되며, 또 job시간이 7초를 넘어섰다면, 그 작업이 끝나자마자 바로 반복실행이 된다.
전체 코드를 정리하자면 다음과 같이 나온다.
const Impt = {
payments: {
1: [
{ imp_id: 11, order_id: 1, amount: 15000 },
{ imp_id: 12, order_id: 2, amount: 25000 },
{ imp_id: 13, order_id: 3, amount: 10000 }
],
2: [
{ imp_id: 14, order_id: 4, amount: 25000 },
{ imp_id: 15, order_id: 5, amount: 45000 },
{ imp_id: 16, order_id: 6, amount: 15000 }
],
3: [
{ imp_id: 17, order_id: 7, amount: 20000 },
{ imp_id: 18, order_id: 8, amount: 30000 }
],
4: [],
5: [],
//...
},
getPayments: page => {
console.log(`http://..?page=${page}`);
return _.delay(500 * 1, Impt.payments[page]);
},
cancelPayment: imp_id => Promise.resolve(`${imp_id}: 취소완료`)
};
const DB = {
getOrders: ids => _.delay(100, [
{ id: 1 },
{ id: 3 },
{ id: 7 }
])
};
async function job(){
const payments = await _.go(
L.range(1, Infinity), /* 언제까지 값을 꺼낼지 모르기때문에 Infinity */
L.map(Impt.getPayments),
L.takeUntil(({length}) => length < 3),
_.flat);
const order_ids = await _.go(
payments,
_.map(p => p.order_id), /* order_id 뽑기 */
DB.getOrders, /* 실제로 결제된 id 반환 */
_.map(({id}) => id)); /* id 뽑아내기*/
await _.go(
payments,
L.reject(p => order_ids.includes(p.order_id)),
L.map(p => p.imp_id),
L.map(Impt.cancelPayment),
_.each(console.log));
}
/* 반복실행 */
(function recur() {
Promise.all([
_.delay(7000, undefined),
job()
]).then(recur);
}) ();
'Web[웹] > ES6+ 함수형 언어' 카테고리의 다른 글
[ES6+: 응용] 프론트엔드에서 활용하기 (2) (0) | 2019.11.05 |
---|---|
[ES6+: 응용] 프론트엔드에서 활용하기 (1) (0) | 2019.11.04 |
[ES6+: 응용] 사용자 정의 객체를 이터러블 프로그래밍으로 다루기 (0) | 2019.11.01 |
[ES6+: 응용] 객체를 이터러블 프로그래밍으로 다루기 (0) | 2019.10.29 |
[ES6+: 응용] map과 filter로 하는 안전한 함수 합성 (0) | 2019.10.29 |