typescript - چگونه با vscode api از کد async (promises؟) استفاده کنیم: withProgress


من از بابت عنوان واقعاً متأسفم، هیچ ایدهای ندارم که چگونه مشکل خود را بهتر توضیح دهم.

من میخواهم 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 است. من نیاز دارم که همزمان باشد، زیرا میخواهم خروجی stdout آن را بخوانم.

بنابراین، مشکل من این است که در حال حاضر ابتدا executeProcess اجرا میشود و وقتی تمام شد، سپس شروع به نمایش نوار پیشرفت میکند. چگونه میتوانم آن را طوری بنویسم که ابتدا نوار پیشرفت نمایش داده شود و در همین حال برنامه در پسزمینه اجرا شود و کار خود را انجام دهد؟

آیا ممکن است بدون نیاز به بازسازی کد من از child_process.spawn با یک callback استفاده کنم؟

پاسخ شماره ۱

بله، فکر میکنم باید کد خود را تغییر دهید تا از الگوی async استفاده کنید، زیرا در غیر این صورت رشته رابط کاربری را که میخواهید در همین حین دیالوگ پیشرفت را نمایش دهد مسدود میکنید.

در اینجا تفاوت بین استفاده از spawn و 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, '');
            });

اگر میخواهید کل نمونه را اجرا کنید، پروژه 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());
    });
}

اگر از ویندوز استفاده نمیکنید، فقط dir /S و c:\\ را با دستور مناسب دیگری که زمان اجرای طولانی دارد جایگزین کنید.

مزیت دیگر استفاده از روش async این است که دکمه لغو میتواند به راحتی برای پایان دادن به فرایند ایجاد شده متصل شود:

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

... و شما همچنین فرصتی برای نشان دادن پیشرفت با بهروزرسانی شی progress دارید.