RxJを使用してブラウザーで次のシナリオを実行する方法:
私が思いついた中間ソリューション:
Rx.Observable
.fromPromise(submitJobToQueue(jobData))
.flatMap(jobQueueData =>
Rx.Observable
.interval(1000)
.delay(5000)
.map(_ => jobQueueData.jobId)
.take(55)
)
.flatMap(jobId => Rx.Observable.fromPromise(pollQueueForResult(jobId)))
.filter(result => result.completed)
.subscribe(
result => console.log('Result', result),
error => console.log('Error', error)
);
takeUntil
を使用できるようになりましたflatMap
の使用法は意味的に正しいですか?たぶん、この全体を書き直して、flatMap
とチェーンしないでください。上から始めて、あなたは観察可能になるという約束を持っています。これにより値が得られたら、特定の応答(成功)を受け取るまで、または特定の時間が経過するまで、1秒に1回呼び出しを行います。この説明の各部分をRxメソッドにマッピングできます。
「これが値を生成したら」= map
/flatMap
(この場合はflatMap
なので、次に来るものも観測可能であり、それらを平坦化する必要があるため)
"1秒あたり1回" = interval
「特定の応答を受け取る」= filter
「または」= amb
「一定の時間が経過しました」= timer
そこから、次のようにつなぎ合わせることができます。
_Rx.Observable
.fromPromise(submitJobToQueue(jobData))
.flatMap(jobQueueData =>
Rx.Observable.interval(1000)
.flatMap(() => pollQueueForResult(jobQueueData.jobId))
.filter(x => x.completed)
.take(1)
.map(() => 'Completed')
.amb(
Rx.Observable.timer(60000)
.flatMap(() => Rx.Observable.throw(new Error('Timeout')))
)
)
.subscribe(
x => console.log('Result', x),
x => console.log('Error', x)
)
;
_
最初の結果が得られたら、それを2つのオブザーバブル間の競合に投影します。1つは成功した応答を受け取ったときに値を生成し、もう1つは一定の時間が経過したときに値を生成します。 2番目のflatMap
があるのは、_.throw
_がオブザーバブルインスタンスに存在しないためであり、_Rx.Observable
_のメソッドはフラット化する必要があるオブザーバブルを返します。
次のように、amb
/timer
コンボは実際にはtimeout
で置き換えることができることがわかります。
_Rx.Observable
.fromPromise(submitJobToQueue(jobData))
.flatMap(jobQueueData =>
Rx.Observable.interval(1000)
.flatMap(() => pollQueueForResult(jobQueueData.jobId))
.filter(x => x.completed)
.take(1)
.map(() => 'Completed')
.timeout(60000, Rx.Observable.throw(new Error('Timeout')))
)
.subscribe(
x => console.log('Result', x),
x => console.log('Error', x)
)
;
_
サンプルにある_.delay
_は、必要なロジックでは説明されていないため省略しましたが、このソリューションに簡単に適合させることができます。
だから、あなたの質問に直接答えるには:
interval
は、サブスクライバーカウントが0になった瞬間に破棄されるため、手動で停止する必要はありません。これは、take(1)
またはamb
/timeout
が完了しました。これがjsbinです 一緒にスローしてソリューションをテストします(pollQueueForResult
に返された値を調整して、目的の成功/タイムアウトを取得できます。時間を短縮するために、時間を10で割っていますテスト)。
@ matt-burnellの優れた回答に対する小さな最適化。次のように、filterおよびtake演算子をfirst演算子に置き換えることができます。
Rx.Observable
.fromPromise(submitJobToQueue(jobData))
.flatMap(jobQueueData =>
Rx.Observable.interval(1000)
.flatMap(() => pollQueueForResult(jobQueueData.jobId))
.first(x => x.completed)
.map(() => 'Completed')
.timeout(60000, Rx.Observable.throw(new Error('Timeout')))
)
.subscribe(
x => console.log('Result', x),
x => console.log('Error', x)
);
また、知らない人のために、flatMap演算子はRxJS 5.0のmergeMapのエイリアスです。
あなたの質問ではありませんが、同じ機能が必要でした
import { takeWhileInclusive } from 'rxjs-take-while-inclusive'
import { of, interval, race, throwError } from 'rxjs'
import { catchError, timeout, mergeMap, delay, switchMapTo } from 'rxjs/operators'
const defaultMaxWaitTimeMilliseconds = 5 * 1000
function isAsyncThingSatisfied(result) {
return true
}
export function doAsyncThingSeveralTimesWithTimeout(
doAsyncThingReturnsPromise,
maxWaitTimeMilliseconds = defaultMaxWaitTimeMilliseconds,
checkEveryMilliseconds = 500,
) {
const subject$ = race(
interval(checkEveryMilliseconds).pipe(
mergeMap(() => doAsyncThingReturnsPromise()),
takeWhileInclusive(result => isAsyncThingSatisfied(result)),
),
of(null).pipe(
delay(maxWaitTimeMilliseconds),
switchMapTo(throwError('doAsyncThingSeveralTimesWithTimeout timeout'))
)
)
return subject$.toPromise(Promise) // will return first result satistieble result of doAsyncThingReturnsPromise or throw error on timeout
}
// mailhogWaitForNEmails
import { takeWhileInclusive } from 'rxjs-take-while-inclusive'
import { of, interval, race, throwError } from 'rxjs'
import { catchError, timeout, mergeMap, delay, switchMap } from 'rxjs/operators'
const defaultMaxWaitTimeMilliseconds = 5 * 1000
export function mailhogWaitForNEmails(
mailhogClient,
numberOfExpectedEmails,
maxWaitTimeMilliseconds = defaultMaxWaitTimeMilliseconds,
checkEveryMilliseconds = 500,
) {
let tries = 0
const mails$ = race(
interval(checkEveryMilliseconds).pipe(
mergeMap(() => mailhogClient.getAll()),
takeWhileInclusive(mails => {
tries += 1
return mails.total < numberOfExpectedEmails
}),
),
of(null).pipe(
delay(maxWaitTimeMilliseconds),
switchMap(() => throwError(`mailhogWaitForNEmails timeout after ${tries} tries`))
)
)
// toPromise returns promise which contains the last value from the Observable sequence.
// If the Observable sequence is in error, then the Promise will be in the rejected stage.
// If the sequence is empty, the Promise will not resolve.
return mails$.toPromise(Promise)
}
// mailhogWaitForEmailAndClean
import { mailhogWaitForNEmails } from './mailhogWaitForNEmails'
export async function mailhogWaitForEmailAndClean(mailhogClient) {
const mails = await mailhogWaitForNEmails(mailhogClient, 1)
if (mails.count !== 1) {
throw new Error(
`Expected to receive 1 email, but received ${mails.count} emails`,
)
}
await mailhogClient.deleteAll()
return mails.items[0]
}
上からAngular/TypeScriptで書き換えられたソリューション:
export interface PollOptions {
interval: number;
timeout: number;
}
const OPTIONS_DEFAULT: PollOptions = {
interval: 5000,
timeout: 60000
};
@Injectable()
class PollHelper {
startPoll<T>(
pollFn: () => Observable<T>, // intermediate polled responses
stopPollPredicate: (value: T) => boolean, // condition to stop polling
options: PollOptions = OPTIONS_DEFAULT): Observable<T> {
return interval(options.interval)
.pipe(
exhaustMap(() => pollFn()),
first(value => stopPollPredicate(value)),
timeout(options.timeout)
);
}
}
例:
pollHelper.startPoll<Response>(
() => httpClient.get<Response>(...),
response => response.isDone()
).subscribe(result => {
console.log(result);
});