Effection Logo

Processes

Effection 3.0 does not ship with a process library out of the box, but what it does do is make writing resources so easy, that you can do it yourself.

Operating system processes are a perfect candidate for modeling as a resource because they match the resource criteria which are:

  1. They are long running
  2. We want to be able to interact with them while they are running.

This section provides a sketch of how you might create a "command" resource for both Deno and Node that will run an operating system command, and then make sure that no matter what happens, that command is shut down properly.

// use-command
import { action, resource, spawn, suspend, createSignal } from "effection";
import { spawn as spawnChild} from "node:child_process";

export function useCommand(...args) {
  return resource(function*(provide) {
    // spawn the OS process
    let proc = spawnChild(...args);

    let stdout = createSignal();
    let stderr = createSignal();


    // create a task that finishes the process finishes
    let exit = yield* spawn(function*() {
      // get the exit code
      let code = yield* action(function*(resolve) {
        proc.on("exit", resolve);
        try {
          yield* suspend();
        } finally {
          proc.off("exit", resolve);
        }
      });

      // close stdin, stdout
      stdout.close();
      stderr.close();

      // return the exitcode
      return code;
    })

    // connect stdin, stdout to signals
    proc.stdout.on("data", stdout.send);
    proc.stderr.on("data", stderr.send);

    try {

      // provide the resource handle
      yield* provide({ exit, stdout, stderr });

    } finally {
      proc.stdout.off("data", stdout.send);
      proc.stderr.off("data", stderr.send);

      proc.kill("SIGINT");

      // wait until the process is fully exited
      yield* exit;
    }
  });
}

using a command

We can now use this command resource to create a process and stream its stdout to the console:


import { main, each } from "effection";
import { useCommand } from "./use-command.mjs";

await main(function*() {
  let ls = yield* useCommand("ls", ["-lh"]);

  for (let data of yield* each(ls.stdout)) {
    console.log(`stdout: ${data}`);
    yield* each.next();
  }

  console.log(`exit: ${yield* ls.exit}`)
});
  • PreviousScope