Skip to content

Consider supporting/merging with the cancellationtoken npm package #855

@AArnott

Description

@AArnott

vscode-jsonrpc defines its own CancellationToken interface which is modeled after the .NET type by the same name. While this is a fine type to use, it's tucked inside an RPC library which doesn't make it particularly nice to use in other settings.

There is a cancellationtoken npm package that focuses just on such a token. It is a more capable token, offering throwIfCancelled(), timeouts, optional reasons for cancellation, an api to race a token against another promise, and combining multiple tokens into one as a race or as a union of all. While it can do everything the .NET version can, the API is designed to be more javascript-esque, so it feels more native.

Given these two options, in some libraries I would tend to prefer the latter token. But where I have to interop with vscode-jsonrpc this complicates the story and leads to potential bugs when the wrong token is given or expected.

Can vscode-jsonrpc be updated to support this cancellationtoken npm package?
In particular, I'm thinking you could do duck-type checking to detect which kind it is and work with both. When creating a token to pass to RPC servers, perhaps you could create a token which fulfills the API obligations of both so that the RPC server can be implemented to receive either one.

Here are adapters I wrote that makes either token look like the other, to demonstrate how simple this is. Note that creating a union type to provide to RPC server methods would probably be more complicated.

import CancellationToken from 'cancellationtoken';
import {
    CancellationToken as vscodeCancellationToken,
    CancellationTokenSource as vscodeCancellationTokenSource,
} from 'vscode-jsonrpc';

export class CancellationTokenAdapters {
    /** Tests whether an object satisfies the {@link vscodeCancellationToken} interface. */
    static IsVSCode(value: any): value is vscodeCancellationToken {
        return vscodeCancellationToken.is(value);
    }

    /** Tests whether an object satisfies the {@link CancellationToken} interface. */
    static IsCancellationToken(value: any): value is CancellationToken {
        return value
            && typeof value.throwIfCancelled === 'function'
            && typeof value.onCancelled === 'function'
            && value.isCancelled !== undefined
            && value.canBeCancelled !== undefined;
    }

    /** Returns a {@link CancellationToken} that is linked to the given {@link vscodeCancellationToken}. */
    static VSCodeToCancellationToken(cancellationToken: vscodeCancellationToken): CancellationToken {
        if (cancellationToken.isCancellationRequested) {
            return CancellationToken.CANCELLED;
        } else if (cancellationToken === vscodeCancellationToken.None) {
            return CancellationToken.CONTINUE;
        } else {
            const result = CancellationToken.create();
            cancellationToken.onCancellationRequested(_ => result.cancel());
            return result.token;
        }
    }

    /** Returns a {@link vscodeCancellationToken} that is linked to the given {@link CancellationToken}. */
    static CancellationTokenToVSCode(cancellationToken: CancellationToken): vscodeCancellationToken {
        if (cancellationToken.isCancelled) {
            return vscodeCancellationToken.Cancelled;
        } else if (cancellationToken.canBeCancelled) {
            const cts = new vscodeCancellationTokenSource();
            cancellationToken.onCancelled(_ => cts.cancel());
            return cts.token;
        } else {
            return vscodeCancellationToken.None;
        }
    }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    feature-requestRequest for new features or functionalityhelp wantedIssues identified as good community contribution opportunities

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions