Hi-Angel
Hi-Angel

Reputation: 5659

PureScript: how to wait for child to exit?

I'm writing a backend, and I need to execute certain command with text passed via stdin, then read the result from stdout. Such utilities provided by Node.ChildProcess module, except I don't see any way to wait till child exits. Which is odd, because there's no point in reading from stdout if the app didn't write anything there yet.

In terms of a minimal example I'm using cat. cat reads text from stdin and writes to stdout.

module Main where

import Prelude

import Data.Maybe (Maybe(..))
import Effect (Effect)
import Effect.Console (log)
import Node.ChildProcess as Proc
import Node.Encoding (Encoding(UTF8))
import Node.Stream as Stream

main :: Effect Unit
main = do
  child :: Proc.ChildProcess <- Proc.spawn "cat" []
  let
    stdin' :: Stream.Writable ()
    stdin' = Proc.stdin child
  _ <- Stream.writeString stdin' UTF8 "hello"
  _ <- Stream.end stdin'

  -- wait for child ?? How?

  let
    stdout' :: Stream.Readable ()
    stdout' = Proc.stdout child
  output :: Maybe String <- Stream.readString stdout' UTF8
  case output of
    Just s -> log s
    _      -> log "nothing"

I want this to print hello, because that's what cat writes to its stdout. Instead unsurprisingly I get "nothing", because by the time Stream.readString is executed cat didn't write anything to stdout yet.

How do I wait for cat to exit?

Upvotes: 0

Views: 67

Answers (2)

Hi-Angel
Hi-Angel

Reputation: 5659

As a complementary answer, it isn't uncommon to have to retrieve the output in the same code that spawned external process and not being able to postpone that to some later time. In this case the on_ hook mentioned in the accepted answer won't work.

The way to do that is by using spawnSync' this way:

module Main where

import Prelude

import Data.Maybe (Maybe(..))
import Effect (Effect)
import Effect.Console (log)
import Node.ChildProcess as Proc
import Node.ChildProcess.Types as Proc
import Node.Encoding (Encoding(UTF8))
import Node.Buffer as Buf

isOkayExitcode :: Proc.Exit -> Boolean
isOkayExitcode (Proc.Normally x) = x == 0
isOkayExitcode _ = false

main :: Effect Unit
main = do
  input :: Buf.Buffer <- Buf.fromString "hello" UTF8
  let
    options :: Proc.SpawnSyncOptions -> Proc.SpawnSyncOptions
    options o = o {input = Just input}
  result <- Proc.spawnSync' "cat" [] options
  case isOkayExitcode result.exitStatus of
    true -> do
      stdout :: String <- Buf.toString UTF8 result.stdout
      log stdout
    false -> log "Program exited with error"

Upvotes: -1

Fyodor Soikin
Fyodor Soikin

Reputation: 80880

When the process exits, Node will fire an "exit" event, which is mapped to PureScript as exitH, and you can attach a listener to it with the on_ function from Node.EventEmitter.

Since your program is all synchronous (runs in Effect), you should put everything that should happen after the child's exit inside the "exit" event handler:

main :: Effect Unit
main = do
  child :: Proc.ChildProcess <- Proc.spawn "cat" []
  let
    stdin' :: Stream.Writable ()
    stdin' = Proc.stdin child
  _ <- Stream.writeString stdin' UTF8 "hello"
  _ <- Stream.end stdin'

  child # on_ Proc.exitH \_ -> do
    let
      stdout' :: Stream.Readable ()
      stdout' = Proc.stdout child
    output :: Maybe String <- Stream.readString stdout' UTF8
    case output of
      Just s -> log s
      _      -> log "nothing"

Upvotes: 2

Related Questions