RxJS-控制多个并行执行

时间:2019-03-08 19:34:50

标签: rxjs redux-observable

我有一个场景,我必须控制多个PTZ摄像机才能拍摄多个角度的照片。因此,例如:
Camera A的角度为A1A2A3
Camera B的角度为B1B2B3B4

移动相机以指向正确的角度,捕获图像并上载图像是返回承诺的异步功能。
moveCamera( angle )
captureImage()
uploadImage()

摄像头必须并行运行,但是每个摄像头所拍摄的角度必须顺序进行。

我某种程度上感觉可以通过RxJS轻松解决,但是我正在努力将它们组合在一起。我能提供的最好的解决方案就是类似的解决方案,通过这种解决方案,相机可以按顺序相互处理。请注意,我使用redux-observable,下面的代码是我在 plain RxJS中能做到的最好的代码。请原谅我的RxJS。

const angles = {
  'Camera A': [ 'A1', 'A2', 'A3' ],
  'Camera B': [ 'B1', 'B2', 'B3', 'B4' ],
}
const cameras = of( [ 'Camera A', 'Camera B' ] );
const cameraRun = cameras.pipe(
  mergeMap( camera => {
    // in redux-observable, I could return an array here
    return of( angles[ camera ] );
  } )
);
cameraRun.pipe(
  concatMap( angle => {
    return moveCamera( angle )
      .then( () => captureImage() )
      .then( () => uploadImage() )
      .then( () => console.log( 'Image success' ) );
  } )
)

对于认识redux-observable的人,我有3个史诗:
RUN_CAMERA_SET_ROUTINE-运行mergeMap内的所有摄像机
RUN_CAMERA_ROUTINE-在mergeMap内为每个摄像机运行所有角度
CAPTURE_IMAGE-在一个concatMap

内部运行上述异步功能

我最初的想法是CAPTURE_IMAGE由于mergeMap生成流而将被“分组”,但是我错了。对于所有摄像机,CAPTURE_IMAGE似乎仍然在每个角度排队。

任何指针都将非常有帮助。

2 个答案:

答案 0 :(得分:1)

您的问题归结为按顺序并行执行其他一些可观察对象,并从Promises中创建可观察对象。

  1. 要并行执行多个Observable,请使用:

    • forkJoin,如果您只想在所有相机动作完成后才发出最终的Observable
    • merge,如果您希望在每次相机操作成功时都发出最终的Observable
  2. 使用concat依次执行多个Observable。

  3. 使用defer从Promise中创建一个Observable,但不要立即执行Promise。

然后,您必须构造要依次执行的Observable数组(单个摄像机的摄像机角度动作)和要并行执行的Observable数组(每个摄像机的摄像机动作列表) )。

这可能是纯RxJS中的代码

import { concat, forkJoin, merge, defer } from 'rxjs';

const cameras = ['Camera A', 'Camera B'];
const cameraAngles = { 'Camera A': ['A1', 'A2', 'A3'], 'Camera B': ['B1', 'B2', 'B3', 'B4'] }

/**
 * Performs a camera action consisting of multiple parts. Returns a Promise.
 * @param camera a camera, I added this parameter because it seemed logic but you don't seem to need it.
 * @param angle a camera angle
 */
function doCameraAction(camera, angle) {
    return moveCamera(angle)
        .then(() => captureImage())
        .then(() => uploadImage())
        .then(() => console.log('Image success'));
}

/**
 * Returns an array of Observables. Each Observable will perform a camera action when subscribed to.
 * @param camera a camera
 * @param angles an array of camera angles
 */
function getCameraActions(camera, angles) {
    // 'defer' is used to defer the creation and thus execution of a camera action until someone subscribes 
    return angles.map(angle => defer(() => doCameraAction(camera, angle)));
}

/** Returns an Observable that will execute multiple camera actions in sequence for the given camera  */
const cameraActions = (camera) => concat(...getCameraActions(camera, cameraAngles[camera]));

// An Observable that will execute some camera actions for each camera in parallel
const multiCameraActions$ = forkJoin(cameras.map(camera => cameraActions(camera))); 
//---------------------- or merge(...cameras.map(camera => cameraActions(camera)));

https://stackblitz.com/edit/rxjs-jex4mt

答案 1 :(得分:0)

我会为此开枪。我在StackBlitz中提出了一个解决方案,以展示我的想法。在控制台上单击之前,单击按钮以开始新的运行。

有关此解决方案的一些要点:

  • 我只使用start$来启动新的运行,这对解决方案并不重要。
  • 我模拟了三个带有不同超时值的照相机答应功能,只是为了说明事情是如何顺序执行的,而是两个照相机如何并行运行。
  • 我还向每个相机功能传递了camera的变量,但这只是使console.log()可以清楚地显示相机在做什么。
  • 我对redux-observable不做任何事情,而是将其保留为rxjs的原样。
  • 我使用concat()将照片的拍摄转换为可观察的序列,而不是像一连串的承诺那样将其留给您-没必要,只是一种不同的处理方式。
  • li>
  • 我将相机留为单独的Observables(cameraA$cameraB$),但是也可以使用一系列相机来完成。

请随意进行分叉并将其更改为更接近您要查找的内容。

这是StackBlitz中的内容:

import { mergeMap, concatMap, tap } from 'rxjs/operators';
import { fromEvent, from, concat, merge, defer } from 'rxjs';

const moveCamera = (camera, angle) => new Promise(
  (resolve, reject) => { 
    setTimeout(() => {
      console.log(`moved: ${camera} angle: ${angle}`);
      resolve();
    }, 1000) }
);

const captureImage = (camera) => new Promise(
  (resolve, reject) => { 
    setTimeout(() => {
      console.log(`${camera} captured image.`);
      resolve();
    }, 100) }
);

const uploadImage = (camera) => new Promise(
  (resolve, reject) => { 
    setTimeout(() => {
      console.log(`${camera} uploaded image.`);
      resolve();
    }, 2000) }
);


const start$ = fromEvent(document.getElementById('start'), 'click');

const takeAPhoto$ = (camera, angle) => concat(
  defer(() => moveCamera(camera, angle)),
  defer(() => captureImage(camera)),
  defer(() => uploadImage(camera))
);

const cameraA$ = from(['A1', 'A2', 'A3']).pipe(
  concatMap(angle => takeAPhoto$('Camera A', angle))
);

const cameraB$ = from(['B1', 'B2', 'B3', 'B4']).pipe(
  concatMap(angle => takeAPhoto$('Camera B', angle))
);

start$.pipe(
  tap(() => console.log('\n\nstart new run')),
  mergeMap(() => merge(cameraA$, cameraB$)),
).subscribe();

我希望这会有所帮助。