如何在浏览器中使用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
链接?答案 0 :(得分:29)
从顶部开始,您已经承诺将您变成一个可观察的。一旦产生一个值,您希望每秒拨一次电话,直到您收到某个响应(成功)或直到经过一定的时间。我们可以将此解释的每个部分映射到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)
)
;
一旦我们获得了初始结果,我们就会将其投射到两个可观察对象之间的竞赛中,一个在收到成功响应时会产生一个值,另一个在一定时间内产生一个值。已经过去了。第二个flatMap
是因为.throw
不存在于可观察的实例上,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)
或{{ 1}} / amb
完成。 Here's the jsbin我一起测试解决方案(您可以调整timeout
中返回的值以获得所需的成功/超时;为了快速测试,时间除以10。
答案 1 :(得分:8)
来自@ 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 的别名。
答案 2 :(得分:1)
不是您的问题,但我需要相同的功能
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]
}
答案 3 :(得分:1)
我们也有相同的用例,下面的code效果很好。
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);
答案 4 :(得分:0)
上面的角度/打字稿重写解决方案:
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);
});