import errorCodes from '../error'; const { ERR_INVALID_OPT_VALUE, } = errorCodes; interface ChunkLinkListItem { chunk: unknown; callback?: (err?: unknown) => void; next: null | ChunkLinkListItem; } const DEFAULT_HWM = 100; function getHighWaterMark(hwm: number) { if (hwm != null) { if (!(isFinite(hwm) && Math.floor(hwm) === hwm) || hwm < 0) { throw new ERR_INVALID_OPT_VALUE('highWaterMark', hwm); } return Math.floor(hwm); } return DEFAULT_HWM; } export interface PoolOptions { highWaterMark?: number; forceDiscard?: boolean; } export class Pool { // 水位警戒值 protected readonly highWaterMark: number; // 强制丢弃(如果当前挤压的数据 >= highWaterMark,值为true时则强制丢掉新的数据) protected readonly forceDiscard: boolean; // 采用单向链表缓存数据 protected chunkLinkListLen = 0; protected chunkLinkListStart: ChunkLinkListItem; protected chunkLinkListEnd: ChunkLinkListItem; constructor({ highWaterMark, forceDiscard, }: PoolOptions) { this.highWaterMark = getHighWaterMark(highWaterMark); this.forceDiscard = !!forceDiscard; } // 从链头开始获取x个数据 private getOriginItems(num = 1): Array { const arr = []; for (let i = 0, start = this.chunkLinkListStart; i < num; i++) { if (!start) { break; } arr.push(start); start = start.next; } return arr; } /** * @return boolean 当前待处理的chunk数量是否超过阈值 * */ addChunk(chunk: unknown, callback?: ChunkLinkListItem['callback']): boolean { // 超过阈值并且设置了"强制丢弃"flag,则丢弃 if (this.chunkLinkListLen + 1 > this.highWaterMark && this.forceDiscard) { return false; } this.chunkLinkListLen += 1; const item: ChunkLinkListItem = { chunk, callback, next: null, } const latestItem = this.chunkLinkListEnd; this.chunkLinkListEnd = item; if (latestItem) { latestItem.next = item; } else { this.chunkLinkListStart = this.chunkLinkListEnd; } return this.chunkLinkListLen < this.highWaterMark; } delChunks(num: number) { const items = this.getOriginItems(num); if (items.length > 0) { this.chunkLinkListStart = items[items.length - 1].next; this.chunkLinkListLen -= items.length; items.forEach(item => item.next = null); } // 链表里没有任何数据了 if (!this.chunkLinkListStart) { this.chunkLinkListEnd = this.chunkLinkListStart; } } getChunks(num: number) { const items = this.getOriginItems(num); return items.map(item => ({ ...item, next: null, })); } }