How to write async code (promises?) with vscode api: withProgress

笑着哭i 提交于 2020-02-02 06:26:28

问题


I'm pretty sorry about the title, I've got no idea how to better describe my problem.

I would like to implement the withProgress API of VSCode, to be able to show progressbar while my code is runnig/progressing. Documentation is here: https://code.visualstudio.com/api/extension-capabilities/common-capabilities#progress-api

I've tried to implement it:

vscode.window.withProgress({
    location: vscode.ProgressLocation.Notification,
    title: "I am long running!",
}, (progress, token) => {
    return new Promise(resolve => {
        const output = executeProcess('sleep 5');
        resolve();
    });
});

executeProcess(...) is a wrapper around npm child_process.spawnSync. I need it to be synchronous, since I'd like to read the stdout of it.

So, my problem is that it's currently running the executeProcess and when it finished, then it starts showing the progressbar. How could I write it the way that it would start showing the progressbar first, meanwhile it's running and doing its job in the background?

Is it possible without the needing of restructuring my code to use to use child_process.spawn with a callback?


回答1:


Yes, I think you have to change your code to use the async pattern, because otherwise you are blocking the UI thread that you want render the progress dialog meanwhile instead.

Here is the difference between using spawn and spawnSync:

The example of how to read from the child process output in the async approach is here:

        let childProcess = spawn(someProcessToSpawn)
            .on("close", (code, signal) => {
                console.log(`Closed: ${code} ${signal}`);
                if (childProcess.killed) { console.log('KILLED'); }
                resolve();
                clearInterval(interval);
            })
            .on("error", err => {
                reject(err);
            });

        childProcess.stdout
            .on("data", (chunk: string | Buffer) => {
                // YOUR CODE GOES HERE
                console.log(`stdout: ${chunk}`);
                progressUpdate = chunk.toString('utf8', 0, 50).replace(/[\r\n]/g, '');
            });

If you want to run the whole example, clone the progress-sample, run npm install and replace the extension.ts content with this code:

'use strict';

import { ExtensionContext, window, commands, ProgressLocation, CancellationToken, Progress } from 'vscode';
import { spawn, spawnSync } from 'child_process';

export function activate(context: ExtensionContext) {
    context.subscriptions.push(commands.registerCommand('extension.startTask', async () => {
        let mode = await window.showQuickPick(['sync', 'async'], { placeHolder: 'Pick mode...' });
        window.withProgress({
            location: ProgressLocation.Notification,
            title: "I am long running",
            cancellable: true
        }, async (progress, token) => {
            token.onCancellationRequested(() => {
                console.log("User canceled the long running operation");
            });

            switch (mode) {
                case undefined:
                    return; // canceled by the user
                case 'sync':
                    return spawnSomethingSync(token);
                case 'async':
                default:
                    return spawnSomethingAsync(progress, token);
            }
        });
    }));
}

/**
 * Synchronous approach
 * @param _token cancellation token (unused in the sync approach)
 */
function spawnSomethingSync(_token: CancellationToken): Promise<void> {
    return new Promise(resolve => {
        console.log('Started...');
        let child = spawnSync('cmd', ['/c', 'dir', '/S'], { cwd: 'c:\\', encoding: 'utf8' });
        console.log(`stdout: ${child.stdout.slice(0, 1000)}`); // otherwise it is too big for the console
        resolve();
    });
}

/**
 * Asynchronous approach
 * @param token cancellation token (triggered by the cancel button on the UI)
 */
function spawnSomethingAsync(progress: Progress<{ message?: string; increment?: number }>, token: CancellationToken): Promise<void> {
    return new Promise<void>((resolve, reject) => {
        if (token.isCancellationRequested) {
            return;
        }

        var progressUpdate = 'Starting up...';
        const interval = setInterval(() => progress.report({ message: progressUpdate }), 500);

        let childProcess = spawn('cmd', ['/c', 'dir', '/S'], { cwd: 'c:\\' })
            .on("close", (code, signal) => {
                console.log(`Closed: ${code} ${signal}`);
                if (childProcess.killed) { console.log('KILLED'); }
                resolve();
                clearInterval(interval);
            })
            .on("error", err => {
                reject(err);
            });

        childProcess.stdout
            .on("data", (chunk: string | Buffer) => {
                // YOUR CODE GOES HERE
                console.log(`stdout: ${chunk}`);
                progressUpdate = chunk.toString('utf8', 0, 50).replace(/[\r\n]/g, '');
            });

        token.onCancellationRequested(_ => childProcess.kill());
    });
}

If you are not on Windows, just replace the dir /S and c:\\ with some other appropriate long running command.

The other benefit of using the async approach is that the Cancel button can be easily hooked up to kill the spawned process:

token.onCancellationRequested(_ => childProcess.kill());

... and you also get a chance to show progress by updating the progress object.



来源:https://stackoverflow.com/questions/58763318/how-to-write-async-code-promises-with-vscode-api-withprogress

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!