stdoutstdinchild-processpurescriptexternal-process

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?


Solution

  • 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"