Skip to content

Commit

Permalink
Add a possibility to cancel a request. Resolves #16 (#17)
Browse files Browse the repository at this point in the history
  • Loading branch information
dhuebner authored Nov 29, 2024
1 parent 0893017 commit 0fcdb39
Show file tree
Hide file tree
Showing 17 changed files with 1,031 additions and 558 deletions.
6 changes: 4 additions & 2 deletions .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,16 @@
"parser": "@typescript-eslint/parser",
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended"
"plugin:@typescript-eslint/recommended",
"plugin:promise/recommended"
],
"parserOptions": {
"ecmaVersion": 6,
"sourceType": "module"
},
"plugins": [
"@typescript-eslint"
"@typescript-eslint",
"promise"
],
"ignorePatterns": [
"**/{node_modules,lib,bin}"
Expand Down
1 change: 1 addition & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"${fileBasename}",
"--config=${workspaceFolder}/jest.config.json",
"--verbose",
"--detectOpenHandles",
"-i",
"--no-cache",
],
Expand Down
100 changes: 60 additions & 40 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"@types/node": "^15.14.0",
"@typescript-eslint/eslint-plugin": "^5.15.0",
"eslint": "^8.11.0",
"eslint-plugin-promise": "^6.1.1",
"jest": "^27.5.1",
"rimraf": "^3.0.2",
"ts-jest": "^27.1.4"
Expand Down
94 changes: 94 additions & 0 deletions packages/vscode-messenger-common/src/cancellation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/******************************************************************************
* Copyright 2024 TypeFox GmbH
* This program and the accompanying materials are made available under the
* terms of the MIT License, which is available in the project root.
******************************************************************************/

import { CancellationToken, Disposable, isNotificationMessage, Message, MessageParticipant, NotificationMessage } from './messages';

/**
* Deferred promise that can be resolved or rejected later.
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export class Deferred<R = any> {
resolve: (value: R) => void;
reject: (reason?: unknown) => void;

result = new Promise<R>((resolve, reject) => {
this.resolve = (arg) => resolve(arg);
this.reject = (err) => reject(err);
});
}

/**
* Implementation of the CancellationToken interface.
* Allows to trigger cancelation.
*/
export class CancellationTokenImpl implements CancellationToken {
private canceled = false;
private listeners: Array<((reason: string) => void)> = [];

public cancel(reason: string): void {
if (this.canceled) {
throw new Error('Request was already canceled.');
}
this.canceled = true;
this.listeners.forEach(callBack => callBack(reason));
this.listeners = [];
}

get isCancellationRequested(): boolean {
return this.canceled;
}

public onCancellationRequested(callBack: (reason: string) => void): Disposable {
this.listeners.push(callBack);
const listeners = this.listeners;
return {
dispose() {
listeners.splice(listeners.indexOf(callBack), 1);
}
};
}
}

const cancelRequestMethod = '$/cancelRequest';

/**
* Internal message type for canceling requests.
*/
export type CancelRequestMessage = NotificationMessage & { method: typeof cancelRequestMethod, params: CancelParams };

/**
* Parameters for canceling a request.
* @param msgId id of the request to cancel
*/
export interface CancelParams {
/**
* msgId id of the request to cancel
*/
msgId: string;
}

/**
* Checks if the given message is a cancel request.
* @param msg message to check
* @returns true if the message is a cancel request
*/
export function isCancelRequestNotification(msg: Message): msg is CancelRequestMessage {
return isNotificationMessage(msg) && msg.method === cancelRequestMethod;
}

/**
* Creates a cancel request message.
* @param receiver receiver of the cancel request
* @param params id of the request to cancel
* @returns new cancel request message
*/
export function createCancelRequestMessage(receiver: MessageParticipant, params: CancelParams): CancelRequestMessage {
return {
method: cancelRequestMethod,
receiver,
params: { msgId: params.msgId }
};
}
1 change: 1 addition & 0 deletions packages/vscode-messenger-common/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@
******************************************************************************/

export * from './messages';
export * from './cancellation';
25 changes: 22 additions & 3 deletions packages/vscode-messenger-common/src/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,13 +147,13 @@ export type RequestType<P, R> = {
/**
* Used to ensure correct typing. Clients must not use this property
*/
readonly _?: [P,R]
readonly _?: [P, R]
};

/**
* Function for handling incoming requests.
*/
export type RequestHandler<P, R> = (params: P, sender: MessageParticipant) => HandlerResult<R>;
export type RequestHandler<P, R> = (params: P, sender: MessageParticipant, cancelable: CancellationToken) => HandlerResult<R>;
export type HandlerResult<R> = R | Promise<R>;

/**
Expand All @@ -176,8 +176,27 @@ export type NotificationHandler<P> = (params: P, sender: MessageParticipant) =>
* Base API for Messenger implementations.
*/
export interface MessengerAPI {
sendRequest<P, R>(type: RequestType<P, R>, receiver: MessageParticipant, params?: P): Promise<R>
sendRequest<P, R>(type: RequestType<P, R>, receiver: MessageParticipant, params?: P, cancelable?: CancellationToken): Promise<R>
onRequest<P, R>(type: RequestType<P, R>, handler: RequestHandler<P, R>): void
sendNotification<P>(type: NotificationType<P>, receiver: MessageParticipant, params?: P): void
onNotification<P>(type: NotificationType<P>, handler: NotificationHandler<P>): void
}

/**
* Interface that allows to check for cancellation and
* set a listener that is called when the request is canceled.
*/
export interface CancellationToken {
readonly isCancellationRequested: boolean;
onCancellationRequested(callBack: (reason: string) => void): Disposable;
}

/**
* Interface for objects that can be disposed.
*/
export interface Disposable {
/**
* Dispose this object.
*/
dispose(): void;
}
Loading

0 comments on commit 0fcdb39

Please sign in to comment.