에픽에서 동일한 통화를 두 번 시작하는 것을 방지하는 방법
내 서사시는 그 때 잠에서 깨어난다.REMOTE_DATA_STARTED
액션이 전송되고 다음 명령을 사용하여 데이터 가져오기action.url
그리고action.owner
.
동일한 소유자/url에게 두 번의 동시 통화를 시작하지 않도록 해야겠습니다.소유자/url에 대한 호출이 완료되면 나중에 동일한 소유자/url에 대해 다른 호출을 시작해도 괜찮다.
기존 요청을 취소하고 싶지 않기 때문에 취소는 여기서 찾는 것이 아니며, 새 요청을 시작하는 것을 방지하고 싶다.
나는 혼혈이 필요한 것 같다.exhaustMap
그리고groupBy
, 하지만 여기서 어디로 가야 할지 모르겠어.
이 시점에서 이건 내 서사시야, 모든 동시통화를 거부하지, 소유자/url에 의한 것이 아니다.
const myEpic = action$ =>
action$.ofType("REMOTE_DATA_STARTED").exhaustMap(action =>
fakeAjaxCall().map(() => {
return { type: "COMPLETED", owner: action.owner, url: action.url };
})
);
Try it Live
나는 실패한 시험 케이스로 이 시험 프로젯을 만들었다.내가 이 일을 할 수 있게 도와줄 수 있니?
https://codesandbox.io/s/l71zq6x8zl
보다시피test1_exhaustMapByActionType_easy
잘 되네, 잘 되네.test2_exhaustMapByActionTypeOwnerAndUrl
그것은 실패한다.
테스트 결과를 보려면 콘솔을 확장하십시오.
groupBy & expastMap으로 우아하게 할 수 있다.
const groupedByExhaustMap = (keySelector, project) =>
source$ => source$.pipe(
groupBy(keySelector),
mergeMap(groupedCalls =>
groupedCalls.pipe(
exhaustMap(project)
)
)
);
const { delay, groupBy, mergeMap, exhaustMap } = Rx.operators;
const groupedByExhaustMap = (keySelector, project) =>
source$ => source$.pipe(
groupBy(keySelector),
mergeMap(groupedCalls =>
groupedCalls.pipe(
exhaustMap(project)
)
)
);
const calls = [ // every call takes 500ms
{startTime: 0, owner: 1, url: 'url1'},
{startTime: 200, owner: 2, url: 'url2'},
{startTime: 400, owner: 1, url: 'url1'}, // dropped
{startTime: 400, owner: 1, url: 'url2'},
{startTime: 600, owner: 1, url: 'url1'}
];
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
const simulateCallsOverTime$ = Rx.Observable.from(calls)
.pipe(
mergeMap(call => Rx.Observable.of(call)
.pipe(
delay(call.startTime)
)
)
);
simulateCallsOverTime$
.pipe(
groupedByExhaustMap(
call => `${call.owner}_${call.url}`,
async call => {
await sleep(500); // http call goes here
return call;
}
)
)
.subscribe(console.log);
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.5.8/Rx.js"></script>
해결책에 접근하는 방법에는 여러 가지가 있다.내 첫 번째 생각은 네가 그 행동을 오너+로 나눌 수 있다는 것이었다.URL 제목 및 다음 항목과 함께 작업:
const myEpic = (action$) => {
const completed$ = new Subject();
const flights = new DefaultMap((pair$) =>
pair$.exhaustMap((action) =>
fakeAjaxCall().map(() => ({
...action,
type: 'COMPLETED',
}))
)
.subscribe((action) => completed$.next(action))
);
action$.ofType('REMOTE_DATA_STARTED')
.subscribe((action) => {
flights.get(`${action.owner}+${action.url}`).next(action);
});
return completed$;
};
그것은 효과가 있지만, 확실히 그것은 새로운 소유자+가 있는 일종의 "기본 지도"를 유지해야 한다.URL 쌍에 새 항목이 표시됨Subject
(빠른 시행을 썼다.통과되는 테스트 사례:
test('myEpic does both drop actions and NOT drop actions for two owner+url pairs', async () => {
const arrayOfAtMost = (action$, limit) => action$.take(limit)
.timeoutWith(1000, Observable.empty())
.toArray().toPromise();
const action$ = new ActionsObservable(
Observable.create((observer) => {
// Jim #1 emits four (4) concurrent calls—we expect only two to be COMPLETED, one per URL
observer.next({ type: 'REMOTE_DATA_STARTED', url: 'google.com', owner: 'jim1' });
observer.next({ type: 'REMOTE_DATA_STARTED', url: 'google.com', owner: 'jim1' });
observer.next({ type: 'REMOTE_DATA_STARTED', url: 'google.org', owner: 'jim1' });
observer.next({ type: 'REMOTE_DATA_STARTED', url: 'google.org', owner: 'jim1' });
// Jim #2 emits two (2) calls at the same time as Jim #1—we expect only one to be COMPLETED, deduped URLs
observer.next({ type: 'REMOTE_DATA_STARTED', url: 'google.biz', owner: 'jim2' });
observer.next({ type: 'REMOTE_DATA_STARTED', url: 'google.biz', owner: 'jim2' });
// Once all of the above calls are completed, Jim #1 and Jim #2 make calls simultaneously
// We expect both to be COMPLETED
setTimeout(() => {
const url = 'https://stackoverflow.com/q/49563059/1267663';
observer.next({ type: 'REMOTE_DATA_STARTED', url, owner: 'jim1' });
observer.next({ type: 'REMOTE_DATA_STARTED', url, owner: 'jim2' });
}, 505);
})
);
const resultant$ = myEpic(action$);
const results = await arrayOfAtMost(resultant$, 5);
expect(results).toEqual([
{ type: 'COMPLETED', url: 'google.com', owner: 'jim1' },
{ type: 'COMPLETED', url: 'google.org', owner: 'jim1' },
{ type: 'COMPLETED', url: 'google.biz', owner: 'jim2' },
{ type: 'COMPLETED', url: 'https://stackoverflow.com/q/49563059/1267663', owner: 'jim1' },
{ type: 'COMPLETED', url: 'https://stackoverflow.com/q/49563059/1267663', owner: 'jim2' },
]);
});
다음을 포함한 전체 솔루션DefaultMap
구현:
const { Observable, Subject } = require('rxjs');
class DefaultMap extends Map {
constructor(initializeValue) {
super();
this._initializeValue = initializeValue || (() => {});
}
get(key) {
if (this.has(key)) {
return super.get(key);
}
const subject = new Subject();
this._initializeValue(subject);
this.set(key, subject);
return subject;
}
}
const fakeAjaxCall = () => Observable.timer(500);
const myEpic = (action$) => {
const completed$ = new Subject();
const flights = new DefaultMap((uniquePair) =>
uniquePair.exhaustMap((action) =>
fakeAjaxCall().map(() => ({
...action,
type: 'COMPLETED',
}))
)
.subscribe((action) => completed$.next(action))
);
action$.ofType('REMOTE_DATA_STARTED')
.subscribe((action) => {
flights.get(`${action.owner}+${action.url}`).next(action);
});
return completed$;
};
* 위의 조각은 실제로 실행할 수 있는 것이 아니라 접히는 것을 원했을 뿐이다.
테스트 사례와 함께 실행 가능한 예제
나는 GitHub에 대한 테스트 케이스와 함께 실행 가능한 예시를 작성했다.
q49563059.js
서사시야q49563059.test.js
테스트 케이스 포함
사용.groupBy
그리고exhaustMap
운영자
나는 기존 연산자를 통해서만 가능하다는 것을 발견하기 위해 테스트와 함께 원래의 솔루션을 작성했고, 그리고 당신이 제안한 것은 다음과 같다.
const myEpic = action$ =>
action$.ofType('REMOTE_DATA_STARTED')
.groupBy((action) => `${action.owner}+${action.url}`)
.flatMap((pair$) =>
pair$.exhaustMap(action =>
fakeAjaxCall().map(() => ({
...action,
type: 'COMPLETED',
}))
)
);
위와 같은 테스트 세트에 대해 실행하면 합격이다.
자, 이제 시작합시다.
그룹비req.owner
, 결과 평탄화:
const myEpic = action$ =>
action$
.ofType("REMOTE_DATA_STARTED")
.groupBy(req => req.owner)
.flatMap(ownerGroup => ownerGroup.groupBy(ownerReq => ownerReq.url))
.flatMap(urlGroup =>
urlGroup.exhaustMap(action =>
fakeAjaxCall().map(() => ({ type: "COMPLETED", owner: action.owner, url: action.url }))
)
)
잊지마.observe.complete();
const test1_exhaustMapByActionType_easy = () => {
const action$ = new ActionsObservable(
Observable.create(observer => {
observer.next({ type: "REMOTE_DATA_STARTED", owner: "ownerX", url: "url1" });
observer.next({ type: "REMOTE_DATA_STARTED", owner: "ownerX", url: "url1" });
setTimeout(() => {
observer.next({ type: "REMOTE_DATA_STARTED", owner: "ownerX", url: "url1" });
observer.complete();
}, 30);
})
);
const emittedActions = [];
const epic$ = myEpic(action$);
epic$.subscribe(action => emittedActions.push(action), null, () => expect("test1_exhaustMapByActionType_easy", 2, emittedActions));
};
여기도 동일:
const test2_exhaustMapByActionTypeOwnerAndUrl = () => {
const action$ = new ActionsObservable(
Observable.create(observer => {
// owner1 emmits 4 concurrent calls, we expect only two to COMPLETED actions; one per URL:
observer.next({ type: "REMOTE_DATA_STARTED", owner: "owner1", url: "url1" });
observer.next({ type: "REMOTE_DATA_STARTED", owner: "owner1", url: "url1" });
observer.next({ type: "REMOTE_DATA_STARTED", owner: "owner1", url: "url2" });
observer.next({ type: "REMOTE_DATA_STARTED", owner: "owner1", url: "url2" });
// owner2 emmits 2 calls at the same time as owner 1. because the two calls
// from owner2 have the same url, we expecty only one COMPLETED action
observer.next({ type: "REMOTE_DATA_STARTED", owner: "owner2", url: "url1" });
observer.next({ type: "REMOTE_DATA_STARTED", owner: "owner2", url: "url1" });
// Once all of the above calls are completed each owner makes one concurrent call
// we expect each call to go throught and generate a COMPLETED action
setTimeout(() => {
observer.next({ type: "REMOTE_DATA_STARTED", owner: "owner1", url: "url1" });
observer.next({ type: "REMOTE_DATA_STARTED", owner: "owner2", url: "url1" });
observer.complete();
}, 30);
})
);
const emittedActions = [];
const epic$ = myEpic(action$);
epic$.subscribe(action => emittedActions.push(action), null, () => expect("test2_exhaustMapByActionTypeOwnerAndUrl", 5, emittedActions));
};
참조URL: https://stackoverflow.com/questions/49563059/how-to-prevent-starting-the-same-call-twice-from-epic
'programing' 카테고리의 다른 글
나는 국가를 위한 일반적인 환원기를 만들었고, 언제 사용해야 하는지, 언제 사용하지 말아야 하는지. (0) | 2022.04.02 |
---|---|
f-string을 사용한 소수점 이후의 고정 자리수 (0) | 2022.04.02 |
Vue Router에서 발송된 후 저장소 데이터를 올바르게 업데이트하지 마십시오. (0) | 2022.04.02 |
Python 함수 정의에서 ->는 무엇을 의미하는가? (0) | 2022.04.02 |
Vuejs v-select에서 개체의 속성에 액세스하는 방법 (0) | 2022.04.02 |