Skip to content

Commit 0fcdb39

Browse files
authored
Add a possibility to cancel a request. Resolves #16 (#17)
1 parent 0893017 commit 0fcdb39

File tree

17 files changed

+1031
-558
lines changed

17 files changed

+1031
-558
lines changed

.eslintrc.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,16 @@
33
"parser": "@typescript-eslint/parser",
44
"extends": [
55
"eslint:recommended",
6-
"plugin:@typescript-eslint/recommended"
6+
"plugin:@typescript-eslint/recommended",
7+
"plugin:promise/recommended"
78
],
89
"parserOptions": {
910
"ecmaVersion": 6,
1011
"sourceType": "module"
1112
},
1213
"plugins": [
13-
"@typescript-eslint"
14+
"@typescript-eslint",
15+
"promise"
1416
],
1517
"ignorePatterns": [
1618
"**/{node_modules,lib,bin}"

.vscode/launch.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
"${fileBasename}",
2727
"--config=${workspaceFolder}/jest.config.json",
2828
"--verbose",
29+
"--detectOpenHandles",
2930
"-i",
3031
"--no-cache",
3132
],

package-lock.json

Lines changed: 60 additions & 40 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
"@types/node": "^15.14.0",
1919
"@typescript-eslint/eslint-plugin": "^5.15.0",
2020
"eslint": "^8.11.0",
21+
"eslint-plugin-promise": "^6.1.1",
2122
"jest": "^27.5.1",
2223
"rimraf": "^3.0.2",
2324
"ts-jest": "^27.1.4"
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
/******************************************************************************
2+
* Copyright 2024 TypeFox GmbH
3+
* This program and the accompanying materials are made available under the
4+
* terms of the MIT License, which is available in the project root.
5+
******************************************************************************/
6+
7+
import { CancellationToken, Disposable, isNotificationMessage, Message, MessageParticipant, NotificationMessage } from './messages';
8+
9+
/**
10+
* Deferred promise that can be resolved or rejected later.
11+
*/
12+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
13+
export class Deferred<R = any> {
14+
resolve: (value: R) => void;
15+
reject: (reason?: unknown) => void;
16+
17+
result = new Promise<R>((resolve, reject) => {
18+
this.resolve = (arg) => resolve(arg);
19+
this.reject = (err) => reject(err);
20+
});
21+
}
22+
23+
/**
24+
* Implementation of the CancellationToken interface.
25+
* Allows to trigger cancelation.
26+
*/
27+
export class CancellationTokenImpl implements CancellationToken {
28+
private canceled = false;
29+
private listeners: Array<((reason: string) => void)> = [];
30+
31+
public cancel(reason: string): void {
32+
if (this.canceled) {
33+
throw new Error('Request was already canceled.');
34+
}
35+
this.canceled = true;
36+
this.listeners.forEach(callBack => callBack(reason));
37+
this.listeners = [];
38+
}
39+
40+
get isCancellationRequested(): boolean {
41+
return this.canceled;
42+
}
43+
44+
public onCancellationRequested(callBack: (reason: string) => void): Disposable {
45+
this.listeners.push(callBack);
46+
const listeners = this.listeners;
47+
return {
48+
dispose() {
49+
listeners.splice(listeners.indexOf(callBack), 1);
50+
}
51+
};
52+
}
53+
}
54+
55+
const cancelRequestMethod = '$/cancelRequest';
56+
57+
/**
58+
* Internal message type for canceling requests.
59+
*/
60+
export type CancelRequestMessage = NotificationMessage & { method: typeof cancelRequestMethod, params: CancelParams };
61+
62+
/**
63+
* Parameters for canceling a request.
64+
* @param msgId id of the request to cancel
65+
*/
66+
export interface CancelParams {
67+
/**
68+
* msgId id of the request to cancel
69+
*/
70+
msgId: string;
71+
}
72+
73+
/**
74+
* Checks if the given message is a cancel request.
75+
* @param msg message to check
76+
* @returns true if the message is a cancel request
77+
*/
78+
export function isCancelRequestNotification(msg: Message): msg is CancelRequestMessage {
79+
return isNotificationMessage(msg) && msg.method === cancelRequestMethod;
80+
}
81+
82+
/**
83+
* Creates a cancel request message.
84+
* @param receiver receiver of the cancel request
85+
* @param params id of the request to cancel
86+
* @returns new cancel request message
87+
*/
88+
export function createCancelRequestMessage(receiver: MessageParticipant, params: CancelParams): CancelRequestMessage {
89+
return {
90+
method: cancelRequestMethod,
91+
receiver,
92+
params: { msgId: params.msgId }
93+
};
94+
}

packages/vscode-messenger-common/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@
55
******************************************************************************/
66

77
export * from './messages';
8+
export * from './cancellation';

packages/vscode-messenger-common/src/messages.ts

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -147,13 +147,13 @@ export type RequestType<P, R> = {
147147
/**
148148
* Used to ensure correct typing. Clients must not use this property
149149
*/
150-
readonly _?: [P,R]
150+
readonly _?: [P, R]
151151
};
152152

153153
/**
154154
* Function for handling incoming requests.
155155
*/
156-
export type RequestHandler<P, R> = (params: P, sender: MessageParticipant) => HandlerResult<R>;
156+
export type RequestHandler<P, R> = (params: P, sender: MessageParticipant, cancelable: CancellationToken) => HandlerResult<R>;
157157
export type HandlerResult<R> = R | Promise<R>;
158158

159159
/**
@@ -176,8 +176,27 @@ export type NotificationHandler<P> = (params: P, sender: MessageParticipant) =>
176176
* Base API for Messenger implementations.
177177
*/
178178
export interface MessengerAPI {
179-
sendRequest<P, R>(type: RequestType<P, R>, receiver: MessageParticipant, params?: P): Promise<R>
179+
sendRequest<P, R>(type: RequestType<P, R>, receiver: MessageParticipant, params?: P, cancelable?: CancellationToken): Promise<R>
180180
onRequest<P, R>(type: RequestType<P, R>, handler: RequestHandler<P, R>): void
181181
sendNotification<P>(type: NotificationType<P>, receiver: MessageParticipant, params?: P): void
182182
onNotification<P>(type: NotificationType<P>, handler: NotificationHandler<P>): void
183183
}
184+
185+
/**
186+
* Interface that allows to check for cancellation and
187+
* set a listener that is called when the request is canceled.
188+
*/
189+
export interface CancellationToken {
190+
readonly isCancellationRequested: boolean;
191+
onCancellationRequested(callBack: (reason: string) => void): Disposable;
192+
}
193+
194+
/**
195+
* Interface for objects that can be disposed.
196+
*/
197+
export interface Disposable {
198+
/**
199+
* Dispose this object.
200+
*/
201+
dispose(): void;
202+
}

0 commit comments

Comments
 (0)