如何使用rxjs进行两个异步调用,无论第一个调用成功与否,都要依次执行。

huangapple go评论107阅读模式
英文:

How to make 2 async calls with rxjs one after the other regardless if the first succeeds or fails

问题

以下是翻译好的部分:

我有一个使用案例,需要进行两个异步调用。这里的顺序很重要。第一个调用需要在开始下一个调用之前完成(或失败)。

下面是一个示例代码,展示了我需要的内容,但它并没有以响应式的方式完成(即不使用rxjs操作符)。

public performDBTasks(): Observable<void> {
    return Observable.create(
        (observer) => {
            this.performDBTask1().subscribe(
                () => {
                    this.performDBTask2().subscribe(
                        () => {
                            observer.next();
                            observer.complete();
                        },
                        () => {
                            observer.error();
                        }
                    );
                },
                (error) => {
                    this.performDBTask2().subscribe(
                        () => {
                            observer.next();
                            observer.complete();
                        },
                        () => {
                            observer.error();
                        }
                    );
                }
            );
        }
    );
}

更新:只是为了澄清,这两个调用类似于HTTP调用,因此它们将返回数据并完成或出错。不会有数据流。

英文:

I have a use case where I need to make two async calls. order is important here. The first call needs to complete (or fail) before starting the next call.

I have a example code below showing what I need but it is not done in a reactive way (ie. using rxjs operators)

public performDBTasks(): Observable&lt;void&gt; {
	return Observable.create(
	(observer)=&gt; {
		this.performDBTask1().subscribe(
		()=&gt; {
			this.performDBTask2().subscribe(
			()=&gt; {
				observer.next();
				observer.complete();
			},
			()=&gt; {
				observer.error();
			});
		},
		(error)=&gt; {
			this.performDBTask2().subscribe(
			()=&gt; {
				observer.next();
				observer.complete();
			},
			()=&gt; {
				observer.error();
			});			
		});	
	});
}

Update: just to clarify the two calls are http like calls so they will return data & complete or error. There will be no stream of data.

答案1

得分: 1

You could use from with concatMap, where it will wait until the previous request has finished.

const { from, concatMap, fetch: { fromFetch }, tap } = rxjs;

const endpoints = [
  // Returns 404
  'https://dummy.restapiexample.com/api/v1/404-error',
  // Returns 200 
  'https://dummy.restapiexample.com/api/v1/employee/1'
];

from(endpoints).pipe(
  // Won't fetch until the previous request has finished.
  concatMap(url => fromFetch(url).pipe(
    tap(url => console.log(`Making request to: ${url.url}`)),
  )),
  tap(resp => console.log(`Code: ${resp.status}`))
).subscribe()
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/7.8.0/rxjs.umd.min.js"></script>
英文:

You could use from with concatMap, where it will wait until the previous request has finished.

<!-- begin snippet: js hide: false console: true babel: false -->

<!-- language: lang-js -->

const { from, concatMap, fetch: { fromFetch }, tap } = rxjs;

const endpoints = [
  // Returns 404
  &#39;https://dummy.restapiexample.com/api/v1/404-error&#39;,
  // Returns 200 
  &#39;https://dummy.restapiexample.com/api/v1/employee/1&#39;
];

from(endpoints).pipe(
  // Won&#39;t fetch until the previous request has finished.
  concatMap(url =&gt; fromFetch(url).pipe(
    tap(url =&gt; console.log(`Making request to: ${url.url}`)),
  )),
  tap(resp =&gt; console.log(`Code: ${resp.status}`))
).subscribe()

<!-- language: lang-html -->

&lt;script src=&quot;https://cdnjs.cloudflare.com/ajax/libs/rxjs/7.8.0/rxjs.umd.min.js&quot;&gt;&lt;/script&gt;

<!-- end snippet -->

答案2

得分: 1

使用switchMapcatchError来同时执行performDBTask2

const { of, throwError, switchMap, tap, catchError } = rxjs;

const performDBTask1 = () =>
  Math.random() > 0.5 // 50/50 出错的机会
    ? of('Success Task 1')
    : throwError('Error Task 1');

const performDBTask2 = () => of('Success Task 2');

performDBTask1().pipe(
  tap({
    next: (val) => { console.log(val); },
    error: (error) => { console.log(error); }
  }),
  catchError(() => performDBTask2()),
  switchMap(() => performDBTask2())
).subscribe((val) => {
  console.log(val);
});

每次点击上面的“运行代码段”按钮时,performDBTask1 失败的概率是50%,但performDBTask2 总是成功的。

英文:

Use a switchMap and a catchError to both return performDBTask2

<!-- begin snippet: js hide: false console: true babel: false -->

<!-- language: lang-js -->

const { of, throwError, switchMap, tap, catchError } = rxjs;

const performDBTask1 = () =&gt;
  Math.random() &gt; .5 // 50/50 chance of erroring
    ? of(&#39;Success Task 1&#39;)
    : throwError(&#39;Error Task 1&#39;);

const performDBTask2 = () =&gt; of(&#39;Success Task 2&#39;);

performDBTask1().pipe(
  tap({
    next: (val) =&gt; { console.log(val); },
    error: (error) =&gt; { console.log(error); }
  }),
  catchError(() =&gt; performDBTask2()),
  switchMap(() =&gt; performDBTask2())
).subscribe((val) =&gt; {
  console.log(val);
});

<!-- language: lang-html -->

&lt;script src=&quot;https://cdnjs.cloudflare.com/ajax/libs/rxjs/7.8.0/rxjs.umd.min.js&quot;&gt;&lt;/script&gt;

<!-- end snippet -->

Everytime you hit the Run code snippet button above there is a 50/50 chance of performDBTask1 failing but performDBTask2 always succeeds.

答案3

得分: 0

你可以使用 switchMaplast 来解决你的问题。

const { Subject, merge, of } = rxjs;
const { switchMap, last, map, catchError } = rxjs.operators;

const first$ = new Subject();
const second$ = new Subject();

const result$ = first$.pipe(
  // 捕获错误并返回一个观察者,该观察者获取“?”并之后完成
  catchError(() => of("?")),
  // 仅在可观察对象完成时发出,并返回最后一个值
  last(),
  // 切换到可观察对象 second$
  switchMap((firstValue) =>
    second$.pipe(
      // 仅用于记录目的 - 记录 first$ 和 second$ 的值
      map((secondValue) => [firstValue, secondValue])
    )
  )
);

result$.subscribe(console.log);

first$.next(1);
first$.next(2);
first$.complete();
second$.next(4);

信息

  1. 根据你的问题,我不知道你想要实现的确切输出是什么。你的问题只描述了异步调用顺序。如果输出不符合你的需求,请留下评论,我可以尝试适应。但请记住要指定/更新你的问题。
  2. 你的示例代码订阅了许多未处理的订阅。尽量避免尽可能多的订阅是一个良好的实践。理想情况下,每个 UI 更新或后端/服务调用最多只有一个订阅。有关如何在 Angular 9 中避免内存泄漏的更多信息,请阅读此问题:如何避免在复杂的rxjs管道中出现内存泄漏
  3. 通过 first$.error("msg") 创建错误是不可能的。相应的 StackOverflow 问题:如何在行为主题上引发错误并继续流程。随时模拟管道上的错误。这种模拟不是回答问题所必需的,所以我会避免实现模拟。
英文:

You can use switchMap and last for your problem.

<!-- begin snippet: js hide: false console: true babel: false -->

<!-- language: lang-js -->

const { Subject, merge, of} = rxjs;
const { switchMap, last, map, catchError } = rxjs.operators;

const first$ = new Subject();
const second$ = new Subject();

const result$ = first$.pipe(
  // catch an error and return an observable that gets &quot;?&quot; and completes afterwards
  catchError(() =&gt; of(&quot;?&quot;)),
  // only emit if observable completes and return last value
  last(),
  // switch to observable second$
  switchMap((firstValue) =&gt;
    second$.pipe(
      // just for logging purpose - log the first$ and second$ value
      map((secondValue) =&gt; [firstValue, secondValue])
    )
  )
);

result$.subscribe(console.log);

first$.next(1);
first$.next(2);
first$.complete();
second$.next(4);

<!-- language: lang-html -->

&lt;script src=&quot;https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.5.3/rxjs.umd.min.js&quot;&gt;&lt;/script&gt;

<!-- end snippet -->

Info

  1. From your question I don't know what exact output you want to achieve. Your qeustions describes only the async call order. If the output does not fit your needs, comment and I can try to adapt. But please keep in mind to specify/udpate your question.
  2. Your sample code subscribes many subscriptions that are not handled. It is a good practice to avoid as many subscriptions as possible. In perfection you have maximum one subscription for every ui update or backend/service call. Read more in this question about how to avoid memory leaks with rxjs pipes
  3. *Creating the error via first$.error(&quot;msg&quot;) is not possible. Corresponding stackoverflow question: How do I throw an error on a behaviour subject and continue the stream. Feel free to be simulate an error on the pipe. The simulation is not needed to answer the question so I will avoid implementing a simulation.

huangapple
  • 本文由 发表于 2023年3月9日 16:20:37
  • 转载请务必保留本文链接:https://go.coder-hub.com/75681983.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定