zx icon indicating copy to clipboard operation
zx copied to clipboard

Feature request: sync method

Open jeremyben opened this issue 2 years ago • 2 comments

I understand that a sync version of $ might not provide the same flexibility than the current async version, for example printing the ouput of a long running command before it ends.

But it might be beneficial for some short running commands that we usually wrap into a utility function, which do not blend in a regular control flow, where await everywhere becomes a pain.

An example:

/**
 * @param {string} bin
 * @returns {Promise<boolean>}
 */
export async function isAvailable(bin) {
	const { exitCode } = await $`command -v ${bin}`.nothrow()
	return !exitCode
}

// elsewhere

if (!(await isAvailable('ffmpeg'))) {
	// ...
}

Execa, who took inspiration from zx for its own $ utility, provides a sync method: https://github.com/sindresorhus/execa/tree/main#synccommand

jeremyben avatar Oct 01 '23 12:10 jeremyben

It's a valid concern, and there can be scenarios where having a synchronous version of a command execution library like shelljs (or the $ tagged template literal used in the example) can be beneficial for simplifying control flow in certain parts of your code.

If you'd like to contribute by adding a synchronous version, here's how you might approach it in JavaScript/TypeScript. Note that this is a simplified example, and you should consider adding error handling and handling more complex use cases based on your specific needs:

import shell from 'shelljs';

/**
 * Synchronously checks if a command is available.
 * @param {string} bin - The command to check.
 * @returns {boolean} - True if the command is available; otherwise, false.
 */
export function isAvailableSync(bin) {
    const result = shell.exec(`command -v ${bin}`, { silent: true });
    return result.code === 0;
}

// Usage
if (isAvailableSync('ffmpeg')) {
    // ...
}

In this example, we use the shelljs library to execute the command synchronously. The silent: true option is used to suppress output to the console, which is similar to the .nothrow() function in your original code.

Remember that synchronous code execution can block the event loop, so use this approach cautiously and only when necessary for specific use cases where synchronous execution is more appropriate.

Sahu-Debasish avatar Oct 06 '23 13:10 Sahu-Debasish

I'd also like to echo the call for some sync methods. One of the situations where it would have been useful is in a module which does a tiny bit of processing when it first initialises, along these lines:

import { $ } from `zx`
const host = $.sync`hostname`.stdout
if (host === ...)

The program as a whole isn't run through zx, so top-level await isn't an option, and I'm not even if that's possible in an imported module.

Requiring everything to be async sometimes introduces complexities like this that are solvable, but are just...annoying...when there's no benefit.

stevage avatar Dec 19 '23 05:12 stevage