Rxjs订阅队列

时间:2018-09-09 16:17:49

标签: javascript firebase rxjs reactive-programming

我在我的角度应用程序中有一个Firebase订阅,它会触发多次。 ich如何实现将任务作为队列处理,以便我可以同步运行每个任务一次?

this.tasks.subscribe(async tasks => {
   for (const x of tasks) 
      await dolongtask(x); // has to be sync
      await removetask(x);
   });

问题在于,当longtask仍在处理时,就会触发subribe事件。

3 个答案:

答案 0 :(得分:2)

我根据您提供的代码进行了一些假设,

  • 其他应用程序(异步地)将任务添加到firebase db,并且此代码正在实现任务处理器。

  • 您的firebase查询将返回所有未处理的任务(在集合中),并且每次添加新任务时都会发出完整列表。

  • 只有运行removeTask()后查询才会删除任务

如果是这样,则需要在处理器之前执行重复数据删除机制。

出于说明的目的,我模拟了带有主题的Firebase查询(将其重命名为TasksQuery $),并在脚本底部模拟了一系列Firebase事件。 我希望它不会太混乱!

console.clear()
const { mergeMap, filter } = rxjs.operators;

// Simulate tasks query  
const tasksQuery$ = new rxjs.Subject();

// Simulate dolongtask and removetask (assume both return promises that can be awaited)
const dolongtask = (task) => {
  console.log( `Processing: ${task.id}`);
  return new Promise(resolve => {
    setTimeout(() => {
      console.log( `Processed: ${task.id}`);
      resolve('done')
    }, 1000);
  });
}
const removeTask = (task) => {
  console.log( `Removing: ${task.id}`);
  return new Promise(resolve => {
    setTimeout(() => {
      console.log( `Removed: ${task.id}`);
      resolve('done')
    }, 200);
  });
}

// Set up queue (this block could be a class in Typescript)
let tasks = [];
const queue$ = new rxjs.Subject();
const addToQueue = (task) => {
  tasks = [...tasks, task];
  queue$.next(task);
}
const removeFromQueue = () => tasks = tasks.slice(1);
const queueContains = (task) => tasks.map(t => t.id).includes(task.id)

// Dedupe and enqueue
tasksQuery$.pipe(
  mergeMap(tasks => tasks), // flatten the incoming task array 
  filter(task => task && !queueContains(task)) // check not in queue
).subscribe(task => addToQueue(task) );

//Process the queue
queue$.subscribe(async task => {
  await dolongtask(task);
  await removeTask(task); // Assume this sends 'delete' to firebase
  removeFromQueue();
});

// Run simulation
tasksQuery$.next([{id:1},{id:2}]);
// Add after delay to show repeated items in firebase
setTimeout(() => {
  tasksQuery$.next([{id:1},{id:2},{id:3}]); 
}, 500);
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.3.2/rxjs.umd.js"></script>

答案 1 :(得分:1)

恕我直言,我会尝试利用rxjs的功能,因为我们已经在这里使用它了,并且避免实现另一个答案建议的自定义排队概念(尽管您当然可以做到)。

如果我们稍微简化一下给定的情况,我们只是有一些可观察到的东西,并且想要对每个发射(依次执行)执行长时间运行的过程。 rxjs允许通过concatMap运算符进行此操作,基本上是开箱即用的:

$data.pipe(concatMap(item => processItem(item))).subscribe();

这仅假设processItem返回一个可观察值。由于您使用了await,因此我假设您的函数当前返回Promises。可以使用from将这些内容微不足道地转换为可观察值。

OP中剩下的唯一需要看的细节是,可观察对象实际上发射了一组项目,我们希望对每个发射中的每个项目执行操作。为此,我们只需使用mergeMap来平化可观察对象。


我们把它们放在一起。请注意,如果您不准备一些存根数据并进行日志记录,则实际的实现仅是两行代码(使用mergeMap + concatMap)。

const { from, interval } = rxjs;
const { mergeMap, concatMap, take, bufferCount, tap } = rxjs.operators;

// Stub for the long-running operation
function processTask(task) {
  console.log("Processing task: ", task);
  return new Promise(resolve => {
    setTimeout(() => {
      console.log("Finished task: ", task);
      resolve(task);
    }, 500 * Math.random() + 300);
  });
}

// Turn processTask into a function returning an observable
const processTask$ = item => from(processTask(item));

// Some stubbed data stream
const tasks$ = interval(250).pipe(
  take(9),
  bufferCount(3),
);

tasks$.pipe(
  tap(task => console.log("Received task: ", task)),
  // Flatten the tasks array since we want to work in sequence anyway
  mergeMap(tasks => tasks),
  // Process each task, but do so consecutively
  concatMap(task => processTask$(task)),
).subscribe(null, null, () => console.log("Done"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.3.2/rxjs.umd.js"></script>

答案 2 :(得分:1)

除了标题“ Rxjs订阅队列”之外,您实际上可以修复异步/等待代码。

问题是async / await在for循环中不能很好地发挥作用,请参见问题Using async/await with a forEach loop

例如,您可以按照@Bergi的答案替换for循环,

Promise.all()

console.clear();
const { interval } = rxjs;
const { take, bufferCount } = rxjs.operators;

function processTask(task) {
  console.log(`Processing task ${task}`);
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(task);
    }, 500 * Math.random() + 300);
  });
}
function removeTask(task) {
  console.log(`Removing task ${task}`);
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(task);
    }, 50);
  });
}

const tasks$ = interval(250).pipe(
  take(10),
  bufferCount(3),
);

tasks$.subscribe(async tasks => {
  await Promise.all(
    tasks.map(async task => {
      await processTask(task); // has to be sync
      await removeTask(task);
      console.log(`Finished task ${task}`);
    })
  );
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.3.2/rxjs.umd.js"></script>


更好的是,您可以调整查询的形状以避免使用for循环,

mergeMap()

console.clear();
const { interval } = rxjs;
const { mergeMap, take, bufferCount } = rxjs.operators;

function processTask(task) {
  console.log(`Processing task ${task}`);
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(task);
    }, 500 * Math.random() + 300);
  });
}
function removeTask(task) {
  console.log(`Removing task ${task}`);
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(task);
    }, 50);
  });
}

const tasks$ = interval(250).pipe(
  take(10),
  bufferCount(3),
);

tasks$
.pipe(mergeMap(tasks => tasks))
.subscribe(
  async task => {
    await processTask(task); // has to be sync
    await removeTask(task);
    console.log(`Finished task ${task}`);
  }
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.3.2/rxjs.umd.js"></script>