1

I am developing a VS Code Extension using TypeScript and need to make some concurrent child-process calls for each folder in the workspace, with a visible progress notification popup. Each new child process will likely take a few seconds to complete.

My approach seems to work fine from the perspective of the child_process calls but the progress bar disappears before all the commands have completed.

Here is a sample of the code I have been working with. Note that my understanding of using an async function with Array.map is that it will result in each await being ran concurrently. Hopefully I am correct with that.

export async function doStuffCmdHandler(channel: vscode.OutputChannel, context: vscode.ExtensionContext): Promise<void> {
    return vscode.window.withProgress({
            title: "Doing Stuff...",
            location: vscode.ProgressLocation.Notification,
        }, async (progress) => {
            if (vscode.workspace.workspaceFolders === undefined) {
                throw Error("Command must be ran from within a VS Code Workspace");
            } else {
                channel.clear();
                vscode.workspace.workspaceFolders.map(async (folder) => {
                    try {
                        // executeCommand() is a wrapper around child_process.exec which returns Promise<string>
                        let results = await executeCommand(folder.uri.path);
                        channel.show();
                        channel.append(results);
                    } catch (err) {
                        channel.show();
                        channel.append(`Error processing ${folder.uri.path}:\n\n`);
                        channel.append(`${(err as Error).message}\n\n`);
                    }
                });
            }
        }
    );
}

If I make my child process calls sequentially with a regular for loop (as per the snippet below) then I don't have any progress bar issues. Howevever, I would prefer to make these calls concurrently:

channel.clear();
for (var folder of vscode.workspace.workspaceFolders) {
    try {
        // executeCommand() is a wrapper around child_process.exec which returns Promise<string>
        let results = await executeCommand(folder.uri.path);
        channel.show();
        channel.append(results);
    } catch (err) {
        channel.show();
        channel.append(`Error processing ${folder.uri.path}:\n\n`);
        channel.append(`${(err as Error).message}\n\n`);
    }
};
jonrsharpe
  • 115,751
  • 26
  • 228
  • 437

1 Answers1

1

There is no use of progress anywhere in your code... Also you are not following the execution of the promises so your function ends before the promises end.

export async function doStuffCmdHandler(channel: vscode.OutputChannel, context: vscode.ExtensionContext): Promise<void> {
    return vscode.window.withProgress({
            title: "Doing Stuff...",
            location: vscode.ProgressLocation.Notification,
        }, async (progress) => {
            if (vscode.workspace.workspaceFolders === undefined) {
                throw Error("Command must be ran from within a VS Code Workspace");
            } else {
                channel.clear();
                const total = vscode.workspace.workspaceFolders.length;
                let done = 0;
   // Return a promise that will end once all promises have finished.
                return Promise.all(vscode.workspace.workspaceFolders.map(async (folder) => {
                    try {
                        // executeCommand() is a wrapper around child_process.exec which returns Promise<string>
                        let results = await executeCommand(folder.uri.path);
                        channel.show();
                        channel.append(results);
                        // Report something, reporting  done vs total
                        progress.report(++done+"/"+total);
                    } catch (err) {
                        channel.show();
                        channel.append(`Error processing ${folder.uri.path}:\n\n`);
                        channel.append(`${(err as Error).message}\n\n`);
                    }
                }));
            }
        }
    );
}
Salketer
  • 14,263
  • 2
  • 30
  • 58
  • Thanks @Salketer. `return Promise.all()` ...so obvious now I see it. I'd been playing with `Promise.all()` and `Promise.allSettled()` to no avail. I'd completely overlooked the fact I wasn't returning it and the function was ending and culling the notification window. I realise there was a lack of use of the progress object in my example code though, that was purposeful. If you don't report any progress, VS Code just gives you an oscillating progress bar. – user16039166 Dec 09 '22 at 15:00
  • Okok, thought you wanted to display an actual progress :) Anyways, this may become handy to have at some point – Salketer Dec 09 '22 at 15:06
  • If I didn't want the fail-fast nature of `Promise.all()`, how might this best be implemented with `Promise.allSettled()`? – user16039166 Dec 09 '22 at 15:28
  • Yes exactly, this should do – Salketer Dec 10 '22 at 16:20