Skip to content

Instantly share code, notes, and snippets.

@dotherightthing
Last active August 30, 2019 10:20
Show Gist options
  • Save dotherightthing/3a1a33e87fcf3575b8a5b28c77784db2 to your computer and use it in GitHub Desktop.
Save dotherightthing/3a1a33e87fcf3575b8a5b28c77784db2 to your computer and use it in GitHub Desktop.
[Node exec] #async #javascript #js #flowcontrol

Node exec

Comparing options, prior to refactoring the broken flow control in my Gulpfile.

Asynchronous, optionally runs a callback function.

Spawns a shell then executes the command within that shell, buffering any generated output.

callback <Function> called with the output when process terminates.

Returns: <ChildProcess> ~ Node.js docs

// Example

const { exec } = require( 'child_process' );

function execTest() {
  exec( 'echo "exec test"', ( error, stdout, stderr ) => {
    if ( error ) {
      console.error( error );
      return;
    }

    console.log( stdout );
    console.error( stderr );
  } );
}

execTest()

Spawns a shell

Spawn in computing refers to a function that loads and executes a new child process.

Creating a new subprocess requires enough memory in which both the child process and the current program can execute.

~ Wikipedia

Executes a command

Any command that you would normally type into terminal, e.g.

echo "hello world"

Optionally runs a callback function

If a callback function is provided, it is called with the arguments (error, stdout, stderr)

The stdout and stderr arguments passed to the callback will contain the stdout and stderr output of the child process. By default, Node.js will decode the output as UTF-8 and pass strings to the callback.

~ Node.js docs

When the process is complete the callback will fire.

stdout refers to a message output to the 'standard output' location. In the terminal, standard output defaults to the user's screen.

Synchronous, no callback option.

The child_process.execSync() method is generally identical to child_process.exec() with the exception that the method will not return until the child process has fully closed.

Returns: <Buffer> | <string> The stdout from the command.

~ Node.js docs

// Example

const { execSync } = require( 'child_process' );

function execSyncTest() {
  const execSyncBuffer = execSync( 'echo "execSync test"' );
  console.log( execSyncBuffer.toString() );
}

execSyncTest();

No callback option

Synchronous scripts block the Event Loop / main thread.

x-node-exec.js shows that the execSync functions are run first, before any of the other test functions.

Buffer.toString()

An important difference between exec and execSync is that the latter returns a Buffer.

Node's Buffer class is a mechanism for reading or manipulating streams of binary data.

In a Buffer the numbers are shown in hexidecimal format.

When we extract string data from a Buffer, we need to convert it into a string, using String.prototype.toString().

3. util.promisify(require('child_process').exec)

Asynchronous, returns a promise.

util.promisify( original )

Takes a function following the common error-first callback style, i.e. taking an (err, value) => ... callback as the last argument, and returns a version that returns promises.

~ Node.js docs

// Example

const util = require( 'util' );
const { exec } = require( 'child_process' );
const execPromise = util.promisify( exec );

async function execPromiseTest() {
  const { stdout, stderr } = await execPromise( 'echo "execPromise test"' );
  console.log( stdout );
  console.error( stderr );
}

execPromiseTest();
// run with node node-exec.js
const util = require( 'util' );
const { exec: execAsyncCallback, execSync } = require( 'child_process' );
const execAsyncPromise = util.promisify( execAsyncCallback );
// Option 1: child_process.exec
function execAsyncCallbackTest( testId ) {
execAsyncCallback( `echo "execAsyncCallback test ${testId}"`, ( error, stdout, stderr ) => {
if ( error ) {
console.error( `error: ${error}` );
return;
}
console.log( stdout );
console.error( stderr );
} );
}
// Option 2: child_process.execSync
function execSyncTest( testId ) {
const execSyncBuffer1 = execSync( `echo "execSync test ${testId}"` );
console.log( execSyncBuffer1.toString() );
}
// Option 3: util.promisify(require('child_process').exec)
async function execAsyncPromiseTest( testId ) {
const { stdout, stderr } = await execAsyncPromise( `echo "execAsyncPromise test ${testId}"` );
console.log( stdout );
console.error( stderr );
}
execAsyncCallbackTest( 1 );
execSyncTest( 1 );
execAsyncPromiseTest( 1 );
execAsyncCallbackTest( 2 );
execSyncTest( 2 );
execAsyncPromiseTest( 2 );
execAsyncCallbackTest( 3 );
execSyncTest( 3 );
execAsyncPromiseTest( 3 );
@dotherightthing
Copy link
Author

Output:

  1. execSync test 1
  2. execSync test 2
  3. execSync test 3
  4. execAsyncCallback test 3
  5. execAsyncPromise test 2
  6. execAsyncCallback test 2
  7. execAsyncPromise test 1
  8. execAsyncCallback test 1
  9. execAsyncPromise test 3

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment