mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-12 23:56:36 +08:00
chore: merge blocksuite source code (#9213)
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
import { describe, expect, test, vi } from 'vitest';
|
||||
|
||||
import { AsyncQueue } from '../async-queue.js';
|
||||
|
||||
describe('async-queue', () => {
|
||||
test('push & pop', async () => {
|
||||
const queue = new AsyncQueue();
|
||||
queue.push(1, 2, 3);
|
||||
expect(queue.length).toBe(3);
|
||||
expect(await queue.next()).toBe(1);
|
||||
expect(await queue.next()).toBe(2);
|
||||
expect(await queue.next()).toBe(3);
|
||||
expect(queue.length).toBe(0);
|
||||
});
|
||||
|
||||
test('await', async () => {
|
||||
const queue = new AsyncQueue<number>();
|
||||
queue.push(1, 2);
|
||||
expect(await queue.next()).toBe(1);
|
||||
expect(await queue.next()).toBe(2);
|
||||
|
||||
let v = -1;
|
||||
|
||||
// setup 2 pop tasks
|
||||
void queue.next().then(next => {
|
||||
v = next;
|
||||
});
|
||||
void queue.next().then(next => {
|
||||
v = next;
|
||||
});
|
||||
|
||||
// Wait for 100ms
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
// v should not be changed
|
||||
expect(v).toBe(-1);
|
||||
|
||||
// push 3, should trigger the first pop task
|
||||
queue.push(3);
|
||||
await vi.waitFor(() => v === 3);
|
||||
|
||||
// push 4, should trigger the second pop task
|
||||
queue.push(4);
|
||||
await vi.waitFor(() => v === 4);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,13 @@
|
||||
import { describe, expect, test } from 'vitest';
|
||||
|
||||
import { throwIfAborted } from '../throw-if-aborted.js';
|
||||
|
||||
describe('throw-if-aborted', () => {
|
||||
test('basic', () => {
|
||||
const abortController = new AbortController();
|
||||
const abortSignal = abortController.signal;
|
||||
expect(throwIfAborted(abortSignal)).toBe(true);
|
||||
abortController.abort('TEST_ABORT');
|
||||
expect(() => throwIfAborted(abortSignal)).toThrowError('TEST_ABORT');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,102 @@
|
||||
export class AsyncQueue<T> {
|
||||
private _queue: T[];
|
||||
|
||||
private _resolveUpdate: (() => void) | null = null;
|
||||
|
||||
private _waitForUpdate: Promise<void> | null = null;
|
||||
|
||||
get length() {
|
||||
return this._queue.length;
|
||||
}
|
||||
|
||||
constructor(init: T[] = []) {
|
||||
this._queue = init;
|
||||
}
|
||||
|
||||
clear() {
|
||||
this._queue = [];
|
||||
}
|
||||
|
||||
find(predicate: (update: T) => boolean) {
|
||||
return this._queue.find(predicate);
|
||||
}
|
||||
|
||||
async next(
|
||||
abort?: AbortSignal,
|
||||
dequeue: (arr: T[]) => T | undefined = a => a.shift()
|
||||
): Promise<T> {
|
||||
const update = dequeue(this._queue);
|
||||
if (update) {
|
||||
return update;
|
||||
} else {
|
||||
if (!this._waitForUpdate) {
|
||||
this._waitForUpdate = new Promise(resolve => {
|
||||
this._resolveUpdate = resolve;
|
||||
});
|
||||
}
|
||||
|
||||
await Promise.race([
|
||||
this._waitForUpdate,
|
||||
new Promise((_, reject) => {
|
||||
if (abort?.aborted) {
|
||||
reject(abort?.reason);
|
||||
}
|
||||
abort?.addEventListener('abort', () => {
|
||||
reject(abort.reason);
|
||||
});
|
||||
}),
|
||||
]);
|
||||
|
||||
return this.next(abort, dequeue);
|
||||
}
|
||||
}
|
||||
|
||||
push(...updates: T[]) {
|
||||
this._queue.push(...updates);
|
||||
if (this._resolveUpdate) {
|
||||
const resolve = this._resolveUpdate;
|
||||
this._resolveUpdate = null;
|
||||
this._waitForUpdate = null;
|
||||
resolve();
|
||||
}
|
||||
}
|
||||
|
||||
remove(predicate: (update: T) => boolean) {
|
||||
const index = this._queue.findIndex(predicate);
|
||||
if (index !== -1) {
|
||||
this._queue.splice(index, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class PriorityAsyncQueue<
|
||||
T extends { id: string },
|
||||
> extends AsyncQueue<T> {
|
||||
constructor(
|
||||
init: T[] = [],
|
||||
readonly priorityTarget: SharedPriorityTarget = new SharedPriorityTarget()
|
||||
) {
|
||||
super(init);
|
||||
}
|
||||
|
||||
override next(abort?: AbortSignal | undefined): Promise<T> {
|
||||
return super.next(abort, arr => {
|
||||
if (this.priorityTarget.priorityRule !== null) {
|
||||
const index = arr.findIndex(update =>
|
||||
this.priorityTarget.priorityRule?.(update.id)
|
||||
);
|
||||
if (index !== -1) {
|
||||
return arr.splice(index, 1)[0];
|
||||
}
|
||||
}
|
||||
return arr.shift();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared priority target can be shared by multiple queues.
|
||||
*/
|
||||
export class SharedPriorityTarget {
|
||||
priorityRule: ((id: string) => boolean) | null = null;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// because AbortSignal.throwIfAborted is not available in abortcontroller-polyfill
|
||||
export function throwIfAborted(abort?: AbortSignal) {
|
||||
if (abort?.aborted) {
|
||||
throw new Error(abort.reason);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
export const MANUALLY_STOP = 'manually-stop';
|
||||
Reference in New Issue
Block a user