[英]RxJs: poll until interval done or correct data received
我如何使用 RxJs 在瀏覽器中執行以下場景:
我想出的中間解決方案:
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
鏈接?從頂部開始,你有一個承諾,你會變成一個可觀察的。 一旦這產生一個值,您希望每秒調用一次,直到您收到某個響應(成功)或直到某個時間段過去。 我們可以將這個解釋的每一部分映射到一個 Rx 方法:
“一旦產生一個值” = map
/ flatMap
(在這種情況下是flatMap
因為接下來發生的也將是可觀察的,我們需要將它們展平)
“每秒一次”= 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)
)
;
一旦我們獲得了最初的結果,我們就將其投射到兩個 observable 之間的競爭中,一個在收到成功響應時產生一個值,一個在經過一定時間后產生一個值。 第二個flatMap
是因為.throw
不存在於 observable 實例中,並且Rx.Observable
上的方法返回一個 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
將被處理,這將在take(1)
或amb
/ timeout
完成時發生。 這是我用來測試解決方案的 jsbin (您可以調整pollQueueForResult
返回的值以獲得所需的成功/超時;為了快速測試,時間已除以 10)。
對@matt-burnell 出色答案的小幅優化。 您可以用第一個運算符替換過濾器和取運算符,如下所示
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]
}
我們也有相同的用例,下面的代碼效果很好。
import { timer, Observable } from "rxjs";
import { scan, tap, switchMapTo, first } from "rxjs/operators";
function checkAttempts(maxAttempts: number) {
return (attempts: number) => {
if (attempts > maxAttempts) {
throw new Error("Error: max attempts");
}
};
}
export function pollUntil<T>(
pollInterval: number,
maxAttempts: number,
responsePredicate: (res: any) => boolean
) {
return (source$: Observable<T>) =>
timer(0, pollInterval).pipe(
scan(attempts => ++attempts, 0),
tap(checkAttempts(maxAttempts)),
switchMapTo(source$),
first(responsePredicate)
);
}
如果嘗試次數已達到限制,則會引發錯誤,導致取消訂閱輸出流。 此外,您只能在不滿足定義為 responsePredicate 的給定條件之前發出 http 請求。
import { of } from "rxjs";
import { pollUntil } from "./poll-until-rxjs";
const responseObj = { body: { inProgress: true } };
const response$ = of(responseObj);
// this is to simulate a http call
response$
.pipe(pollUntil(1000, 3, ({ body }) => !body.inProgress))
.subscribe(({ body }) => console.log("Response body: ", body));
setTimeout(() => (responseObj.body.inProgress = false), 1500);
從上面重寫的角度/打字稿解決方案:
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);
});
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.