rxpoweredup
Version:
A Typescript RxJS-based library for controlling LEGO Powered UP hubs & peripherals.
97 lines (96 loc) • 3.54 kB
JavaScript
import { TimeoutError, filter, finalize, merge, of, retry, switchMap, take, throwError, timeout, timer } from 'rxjs';
import { formatMessageForDump } from '../../../helpers';
import { GenericError } from '../../../errors';
export class TaskQueue {
channel;
messageSendTimeout;
maxMessageSendAttempts;
initialMessageSendRetryDelay;
logger;
genericErrorsStream;
taskVisitor;
isDisposed = false;
queue = [];
constructor(channel, messageSendTimeout, maxMessageSendAttempts, initialMessageSendRetryDelay, logger, genericErrorsStream, taskVisitor) {
this.channel = channel;
this.messageSendTimeout = messageSendTimeout;
this.maxMessageSendAttempts = maxMessageSendAttempts;
this.initialMessageSendRetryDelay = initialMessageSendRetryDelay;
this.logger = logger;
this.genericErrorsStream = genericErrorsStream;
this.taskVisitor = taskVisitor;
}
enqueueTask(task) {
if (this.isDisposed) {
throw new Error('Task queue is already disposed');
}
task.accept(this.taskVisitor);
const lastTask = this.queue.at(-1);
this.queue.push(task);
if (lastTask) {
lastTask.result
.pipe(take(1), finalize(() => this.executeTask(task)))
.subscribe();
}
else {
this.executeTask(task);
}
}
dispose() {
if (this.isDisposed) {
throw new Error('Task queue is already disposed');
}
this.isDisposed = true;
this.queue.forEach((i) => {
i.discard();
i.dispose();
});
this.queue.splice(0, this.queue.length);
this.logger.debug('Task queue disposed');
this.taskVisitor.dispose();
}
executeTask(task) {
of(null)
.pipe(switchMap(() => {
return merge(task.execute(this.channel), this.genericErrorsStream.pipe(filter((e) => e.commandType === task.message.header.messageType), switchMap((error) => {
return throwError(() => new GenericError(error.code, error.commandType));
})));
}), timeout(this.messageSendTimeout), retry({ delay: this.createRetryConfig(task) }), take(1))
.subscribe({
complete: () => {
this.removeTaskFromQueue(task);
},
error: (e) => {
task.emitError(e);
this.removeTaskFromQueue(task);
},
});
}
removeTaskFromQueue(task) {
const index = this.queue.indexOf(task);
if (index >= 0) {
this.queue.splice(index, 1);
task.dispose();
}
}
createRetryConfig(task) {
return (error, retryCount) => {
if (error instanceof TimeoutError) {
if (retryCount >= this.maxMessageSendAttempts) {
this.logMaxRetriesReachedError(task);
return throwError(() => error);
}
this.logTimeoutError(task);
const delayMs = Math.pow(2, retryCount - 1) * this.initialMessageSendRetryDelay;
return timer(delayMs);
}
return throwError(() => error);
};
}
logTimeoutError(task) {
this.logger.warn(`send timeout: ${formatMessageForDump(task.message)}, will retry`);
}
logMaxRetriesReachedError(task) {
this.logger.error(`failed to send ${formatMessageForDump(task.message)}`);
}
}