【问题标题】:How to write async code (promises?) with vscode api: withProgress如何使用 vscode api 编写异步代码(承诺?):withProgress
【发布时间】:2020-03-04 21:28:50
【问题描述】:

对于标题,我很抱歉,我不知道如何更好地描述我的问题。

我想实现 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();
    });
});

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

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

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

【问题讨论】:

    标签: typescript visual-studio-code vscode-extensions


    【解决方案1】:

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

    这里是使用spawnspawnSync的区别:

    async方法中如何从子进程输出中读取的例子在这里:

            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, '');
                });
    

    如果要运行整个示例,请克隆 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());
        });
    }
    

    如果您不在 Windows 上,只需将 dir /Sc:\\ 替换为其他适当的长时间运行命令即可。

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

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

    ...您还可以通过更新progress 对象来显示进度。

    【讨论】:

    • 这个答案的质量是惊人的。非常感谢。
    猜你喜欢
    • 2016-01-08
    • 1970-01-01
    • 1970-01-01
    • 2013-02-09
    • 1970-01-01
    • 2019-02-23
    • 1970-01-01
    • 2017-07-16
    • 2021-04-28
    相关资源
    最近更新 更多