我在我的角度应用程序中有一个Firebase订阅,它会触发多次。 ich如何实现将任务作为队列处理,以便我可以同步运行每个任务一次?
this.tasks.subscribe(async tasks => {
for (const x of tasks)
await dolongtask(x); // has to be sync
await removetask(x);
});
问题在于,当longtask仍在处理时,就会触发subribe事件。
答案 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>