UNPKG

rxpoweredup

Version:

A Typescript RxJS-based library for controlling LEGO Powered UP hubs & peripherals.

97 lines (96 loc) 3.54 kB
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)}`); } }