Как написать асинхронный код (обещания?) С vscode api: withProgress - PullRequest
2 голосов
/ 08 ноября 2019

Мне очень жаль по поводу названия, я не знаю, как лучше описать мою проблему.

Я хотел бы реализовать API-интерфейс withProgress VSCode, чтобы иметь возможность отображать индикатор выполнения, пока мойкод запускается / прогрессирует. Документация здесь: 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, и когда он закончил, он начинает показывать индикатор выполнения. Как я мог написать так, чтобы он сначала показывал индикатор выполнения, а он работает и работает в фоновом режиме?

Возможно ли без необходимости перестройки моего кода использовать дочерний_процесс.spawn с обратным вызовом?

1 Ответ

2 голосов
/ 08 ноября 2019

Да, я думаю, что вы должны изменить свой код, чтобы использовать шаблон async , потому что в противном случае вы блокируете поток пользовательского интерфейса, для которого вы хотите вместо этого отображать диалог прогресса.

Здесьразница между использованием spawn и spawnSync:

vscode.window.withProgress calling spawn vs spawnSync

Пример чтения из вывода дочернего процесса в Подход 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 и замените расширение . 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 /S и c:\\ другой соответствующей длительной командой.

Другое преимущество использования подхода async заключается в том, что кнопка «Отмена» может быть легко подключена для уничтожения порожденного процесса:

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

... и у вас также есть шанс показать прогресспутем обновления объекта progress.

...