如何使用 vscode api 编写异步代码(承诺?):withProgress

ori*_*and 5 typescript visual-studio-code vscode-extensions

我对标题感到非常抱歉,我不知道如何更好地描述我的问题。

我想实现 VSCode 的 withProgress API,以便能够在我的代码运行/进行时显示进度条。文档在这里:https : //code.visualstudio.com/api/extension-capabilities/common-capabilities#progress-api

我试图实现它:

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

executeProcess(...) 是 npm child_process.spawnSync 的包装器。我需要它是同步的,因为我想阅读它的标准输出。

所以,我的问题是它当前正在运行 executeProcess,当它完成时,它开始显示进度条。我怎么能写成它首先开始显示进度条的方式,同时它正在运行并在后台完成它的工作?

是否有可能不需要重构我的代码来使用 child_process.spawn 和回调?

Jan*_*jsi 10

是的,我认为您必须更改代码以使用异步模式,否则您将阻塞要同时呈现进度对话框的 UI 线程。

这是使用spawn和之间的区别spawnSync

vscode.window.withProgress 调用 spawn vs spawnSync

如何在异步方法中读取子进程输出的示例如下:

        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, '');
            });
Run Code Online (Sandbox Code Playgroud)

如果要运行整个示例,请克隆progress-sample,运行npm install并使用以下代码替换extension.ts内容:

'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());
    });
}
Run Code Online (Sandbox Code Playgroud)

如果您不在 Windows 上,只需将dir /S和替换为c:\\其他一些适当的长时间运行的命令。

使用异步方法的另一个好处是可以轻松连接取消按钮以终止生成的进程:

token.onCancellationRequested(_ => childProcess.kill());
Run Code Online (Sandbox Code Playgroud)

...您还有机会通过更新progress对象来显示进度。