Skip to content

Commit

Permalink
feat: CancelableTask&Fallback
Browse files Browse the repository at this point in the history
  • Loading branch information
MliKiowa committed Jan 3, 2025
1 parent 4637414 commit be9b68a
Show file tree
Hide file tree
Showing 3 changed files with 162 additions and 30 deletions.
107 changes: 107 additions & 0 deletions src/common/cancel-task.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
type TaskExecutor<T> = (resolve: (value: T | PromiseLike<T>) => void, reject: (reason?: any) => void, onCancel: (callback: () => void) => void) => void;

export class CancelableTask<T> {
private promise: Promise<T>;
private cancelCallback: (() => void) | null = null;
private isCanceled = false;
private cancelListeners: Array<() => void> = [];

constructor(executor: TaskExecutor<T>) {
this.promise = new Promise<T>((resolve, reject) => {
const onCancel = (callback: () => void) => {
this.cancelCallback = callback;
};

executor(
(value) => {
if (!this.isCanceled) {
resolve(value);
}
},
(reason) => {
if (!this.isCanceled) {
reject(reason);
}
},
onCancel
);
});
}

public cancel() {
if (this.cancelCallback) {
this.cancelCallback();
}
this.isCanceled = true;
this.cancelListeners.forEach(listener => listener());
}

public isTaskCanceled(): boolean {
return this.isCanceled;
}

public onCancel(listener: () => void) {
this.cancelListeners.push(listener);
}

public then<TResult1 = T, TResult2 = never>(
onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | undefined | null,
onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null
): Promise<TResult1 | TResult2> {
return this.promise.then(onfulfilled, onrejected);
}

public catch<TResult = never>(
onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | undefined | null
): Promise<T | TResult> {
return this.promise.catch(onrejected);
}

public finally(onfinally?: (() => void) | undefined | null): Promise<T> {
return this.promise.finally(onfinally);
}

[Symbol.asyncIterator]() {
return {
next: () => this.promise.then(value => ({ value, done: true })),
};
}
}


async function demoAwait() {
const executor: TaskExecutor<number> = (resolve, reject, onCancel) => {
let count = 0;
const intervalId = setInterval(() => {
count++;
console.log(`Task is running... Count: ${count}`);
if (count === 5) {
clearInterval(intervalId);
resolve(count);
}
}, 1000);

onCancel(() => {
clearInterval(intervalId);
console.log('Task has been canceled.');
reject(new Error('Task was canceled'));
});
};

const task = new CancelableTask(executor);

task.onCancel(() => {
console.log('Cancel listener triggered.');
});

setTimeout(() => {
task.cancel(); // 取消任务
}, 6000);

try {
const result = await task;
console.log(`Task completed with result: ${result}`);
} catch (error) {
console.error('Task failed:', error);
}
}
26 changes: 26 additions & 0 deletions src/common/fall-back.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
type Handler<T> = () => T | Promise<T>;

export class Fallback<T> {
private handlers: Handler<T>[] = [];

add(handler: Handler<T>): this {
this.handlers.push(handler);
return this;
}

// 执行处理程序链
async run(): Promise<T> {
const errors: Error[] = [];
for (const handler of this.handlers) {
try {
const result = await handler();
if (result !== undefined) {
return result;
}
} catch (error) {
errors.push(error instanceof Error ? error : new Error(String(error)));
}
}
throw new AggregateError(errors, 'All handlers failed');
}
}
59 changes: 29 additions & 30 deletions src/core/apis/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { InstanceContext, NapCatCore, ProfileBizType } from '..';
import { solveAsyncProblem } from '@/common/helper';
import { promisify } from 'node:util';
import { LRUCache } from '@/common/lru-cache';
import { Fallback } from '@/common/fall-back';

export class NTQQUserApi {
context: InstanceContext;
Expand Down Expand Up @@ -169,46 +170,44 @@ export class NTQQUserApi {
return skey;
}

async getUidByUinV2(Uin: string) {
if (!Uin) {
async getUidByUinV2(uin: string) {
if (!uin) {
return '';
}
const services = [
() => this.context.session.getUixConvertService().getUid([Uin]).then((data) => data.uidInfo.get(Uin)).catch(() => undefined),
() => promisify<string, string[], Map<string, string>>
(this.context.session.getProfileService().getUidByUin)('FriendsServiceImpl', [Uin]).then((data) => data.get(Uin)).catch(() => undefined),
() => this.context.session.getGroupService().getUidByUins([Uin]).then((data) => data.uids.get(Uin)).catch(() => undefined),
() => this.getUserDetailInfoByUin(Uin).then((data) => data.detail.uid).catch(() => undefined),
];
let uid: string | undefined = undefined;
for (const service of services) {
uid = await service();
if (uid && uid.indexOf('*') == -1 && uid !== '') {
break;
}

let isValidUin = (uin: string | undefined) => {
if (uin !== undefined && uin !== '0' && uin !== '') { return uin; } throw new Error('uin is invalid');
}

const fallback = new Fallback<string>()
.add(async () => isValidUin(await this.context.session.getUixConvertService().getUid([uin]).then((data) => data.uidInfo.get(uin))))
.add(() =>isValidUin(this.context.session.getProfileService().getUidByUin('FriendsServiceImpl', [uin]).get(uin)))
.add(async () => isValidUin(await this.context.session.getGroupService().getUidByUins([uin]).then((data) => data.uids.get(uin))))
.add(async () => isValidUin(await this.getUserDetailInfoByUin(uin).then((data) => data.detail.uid)));

const uid = await fallback.run().catch(() => '0');
return uid ?? '';
}

async getUinByUidV2(Uid: string) {
if (!Uid) {
async getUinByUidV2(uid: string) {
if (!uid) {
return '0';
}
const services = [
() => this.context.session.getUixConvertService().getUin([Uid]).then((data) => data.uinInfo.get(Uid)).catch(() => undefined),
() => this.context.session.getGroupService().getUinByUids([Uid]).then((data) => data.uins.get(Uid)).catch(() => undefined),
() => promisify<string, string[], Map<string, string>>
(this.context.session.getProfileService().getUinByUid)('FriendsServiceImpl', [Uid]).then((data) => data.get(Uid)).catch(() => undefined),
() => this.core.apis.FriendApi.getBuddyIdMap(true).then((data) => data.getKey(Uid)).catch(() => undefined),
() => this.getUserDetailInfo(Uid).then((data) => data.uin).catch(() => undefined),
];
let uin: string | undefined = undefined;
for (const service of services) {
uin = await service();
if (uin && uin !== '0' && uin !== '') {
break;

let isValidUid = (uid: string | undefined) => {
if (uid !== undefined && uid.indexOf('*') === -1 && uid !== '') {
return uid;
}
throw new Error('uid is invalid');
}

const fallback = new Fallback<string>()
.add(async () => isValidUid(await this.context.session.getUixConvertService().getUin([uid]).then((data) => data.uinInfo.get(uid))))
.add(() =>isValidUid(this.context.session.getProfileService().getUinByUid('FriendsServiceImpl', [uid]).get(uid)))
.add(async () => isValidUid(await this.context.session.getGroupService().getUinByUids([uid]).then((data) => data.uins.get(uid))))
.add(async () => isValidUid(await this.getUserDetailInfo(uid).then((data) => data.uin)));

const uin = await fallback.run().catch(() => '0');
return uin ?? '0';
}

Expand Down

0 comments on commit be9b68a

Please sign in to comment.