Skip to content

Commit fb0d679

Browse files
Migrate to eventH-style events API (#40)
1 parent b0d710e commit fb0d679

File tree

4 files changed

+140
-72
lines changed

4 files changed

+140
-72
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ Breaking changes:
2727
2828
exit' :: forall a. Int -> Effect a
2929
```
30+
- Bump `node-streams` to `v8.0.0` (#40 by @JordanMartinez)
31+
- Migrate `onEventName` to `eventH`-style event handling API (#40 by @JordanMartinez)
3032

3133
New features:
3234
- Add missing APIs (#39 by @JordanMartinez)

bower.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@
1616
"purescript-foreign-object": "^4.0.0",
1717
"purescript-foreign": "^7.0.0",
1818
"purescript-maybe": "^6.0.0",
19-
"purescript-node-streams": "^7.0.0",
19+
"purescript-node-event-emitter": "https://github.com/purescript-node/purescript-node-event-emitter.git#^3.0.0",
20+
"purescript-node-streams": "^8.0.0",
2021
"purescript-posix-types": "^6.0.0",
2122
"purescript-prelude": "^6.0.0",
2223
"purescript-unsafe-coerce": "^6.0.0"

src/Node/Process.js

+1-36
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,6 @@
11
import process from "process";
22

3-
export function onBeforeExit(callback) {
4-
return () => {
5-
process.on("beforeExit", callback);
6-
};
7-
}
8-
9-
export function onExit(callback) {
10-
return () => {
11-
process.on("exit", code => {
12-
callback(code)();
13-
});
14-
};
15-
}
16-
17-
export function onUncaughtException(callback) {
18-
return () => {
19-
process.on("uncaughtException", error => {
20-
callback(error)();
21-
});
22-
};
23-
}
24-
25-
export function onUnhandledRejection(callback) {
26-
return () => {
27-
process.on("unhandledRejection", (error, promise) => {
28-
callback(error)(promise)();
29-
});
30-
};
31-
}
32-
33-
export function onSignalImpl(signal) {
34-
return callback => () => {
35-
process.on(signal, callback);
36-
};
37-
}
38-
3+
export { process };
394
export const abortImpl = process.abort ? () => process.abort() : null;
405
export const argv = () => process.argv.slice();
416
export const argv0 = () => process.argv0;

src/Node/Process.purs

+135-35
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,18 @@
11
-- | Bindings to the global `process` object in Node.js. See also [the Node API documentation](https://nodejs.org/api/process.html)
22
module Node.Process
3-
( onBeforeExit
4-
, onExit
5-
, onSignal
6-
, onUncaughtException
7-
, onUnhandledRejection
3+
( Process
4+
, process
5+
, beforeExitH
6+
, disconnectH
7+
, exitH
8+
, messageH
9+
, rejectionHandledH
10+
, uncaughtExceptionH
11+
, unhandledRejectionH
12+
, mkSignalH
13+
, mkSignalH'
14+
, warningH
15+
, workerH
816
, abort
917
, argv
1018
, argv0
@@ -71,51 +79,143 @@ import Data.Nullable (Nullable, toMaybe)
7179
import Data.Posix (Pid)
7280
import Data.Posix.Signal (Signal)
7381
import Data.Posix.Signal as Signal
82+
import Data.String as String
7483
import Effect (Effect)
7584
import Effect.Exception (Error)
76-
import Effect.Uncurried (EffectFn1, EffectFn2, EffectFn3, EffectFn4, mkEffectFn1, runEffectFn1, runEffectFn2, runEffectFn3, runEffectFn4)
85+
import Effect.Uncurried (EffectFn1, EffectFn2, EffectFn3, EffectFn4, mkEffectFn1, mkEffectFn2, runEffectFn1, runEffectFn2, runEffectFn3, runEffectFn4)
7786
import Foreign (Foreign)
7887
import Foreign.Object as FO
88+
import Node.EventEmitter (EventHandle(..))
89+
import Node.EventEmitter.UtilTypes (EventHandle0, EventHandle1, EventHandle2)
7990
import Node.Platform (Platform)
8091
import Node.Platform as Platform
8192
import Node.Stream (Readable, Writable)
8293
import Prim.Row as Row
8394

84-
-- | Register a callback to be performed when the event loop empties, and
85-
-- | Node.js is about to exit. Asynchronous calls can be made in the callback,
86-
-- | and if any are made, it will cause the process to continue a little longer.
87-
foreign import onBeforeExit :: Effect Unit -> Effect Unit
95+
foreign import data Process :: Type
8896

89-
-- | Register a callback to be performed when the process is about to exit.
90-
-- | Any work scheduled via asynchronous calls made here will not be performed
91-
-- | in time.
97+
foreign import process :: Process
98+
99+
-- | The 'beforeExit' event is emitted when Node.js empties its event loop and has no additional work to schedule.
100+
-- | Normally, the Node.js process will exit when there is no work scheduled, but a listener registered on the
101+
-- | 'beforeExit' event can make asynchronous calls, and thereby cause the Node.js process to continue.
102+
-- |
103+
-- | The listener callback function is invoked with the value of process.exitCode passed as the only argument.
104+
-- | The 'beforeExit' event is not emitted for conditions causing explicit termination,
105+
-- | such as calling `process.exit()` or uncaught exceptions.
106+
-- | The 'beforeExit' should not be used as an alternative to the 'exit' event unless the
107+
-- | intention is to schedule additional work.
108+
beforeExitH :: EventHandle1 Process Int
109+
beforeExitH = EventHandle "beforeExit" mkEffectFn1
110+
111+
disconnectH :: EventHandle0 Process
112+
disconnectH = EventHandle "disconnect" identity
113+
114+
-- | The 'exit' event is emitted when the Node.js process is about to exit as a result of either:
115+
-- | - The process.exit() method being called explicitly;
116+
-- | - The Node.js event loop no longer having any additional work to perform.
117+
-- |
118+
-- | Listener functions **must** only perform **synchronous** operations.
119+
-- | The Node.js process will exit immediately after calling the 'exit' event listeners causing
120+
-- | any additional work still queued in the event loop to be abandoned.
121+
-- | (Maintainer note: I believe the above translates to
122+
-- | "Only synchronous (i.e. `Effect`) code can be run in the resulting handler.
123+
-- | If you need asynchronous (i.e. `Aff`) code, use `beforeExitH`.")
124+
-- |
125+
-- | There is no way to prevent the exiting of the event loop at this point, and once all 'exit'
126+
-- | listeners have finished running the Node.js process will terminate.
127+
-- | The listener callback function is invoked with the exit code specified either by the
128+
-- | `process.exitCode` property, or the exitCode argument passed to the `process.exit()` method.
129+
exitH :: EventHandle1 Process Int
130+
exitH = EventHandle "exit" mkEffectFn1
131+
132+
messageH :: EventHandle Process (Foreign -> Maybe Foreign -> Effect Unit) (EffectFn2 Foreign (Nullable Foreign) Unit)
133+
messageH = EventHandle "message" \cb -> mkEffectFn2 \a b -> cb a (toMaybe b)
134+
135+
-- | The 'rejectionHandled' event is emitted whenever a Promise has been rejected and an error handler was attached to it (using promise.catch(), for example) later than one turn of the Node.js event loop.
136+
-- |
137+
-- | The Promise object would have previously been emitted in an 'unhandledRejection' event, but during the course of processing gained a rejection handler.
138+
-- |
139+
-- | There is no notion of a top level for a Promise chain at which rejections can always be handled. Being inherently asynchronous in nature, a Promise rejection can be handled at a future point in time, possibly much later than the event loop turn it takes for the 'unhandledRejection' event to be emitted.
140+
-- |
141+
-- | Another way of stating this is that, unlike in synchronous code where there is an ever-growing list of unhandled exceptions, with Promises there can be a growing-and-shrinking list of unhandled rejections.
142+
-- |
143+
-- | In synchronous code, the 'uncaughtException' event is emitted when the list of unhandled exceptions grows.
144+
-- |
145+
-- | In asynchronous code, the 'unhandledRejection' event is emitted when the list of unhandled rejections grows, and the 'rejectionHandled' event is emitted when the list of unhandled rejections shrinks.
146+
rejectionHandledH :: EventHandle1 Process Foreign
147+
rejectionHandledH = EventHandle "rejectionHandled" mkEffectFn1
148+
149+
-- | Args:
150+
-- | - `err` <Error> The uncaught exception.
151+
-- | - `origin` <string> Indicates if the exception originates from an unhandled rejection or from a synchronous error.
152+
-- | Can either be 'uncaughtException' or 'unhandledRejection'.
153+
-- | The latter is used when an exception happens in a Promise based async context (or if a Promise is rejected)
154+
-- | and `--unhandled-rejections` flag set to `strict` or `throw` (which is the default) and
155+
-- | the rejection is not handled, or when a rejection happens during the command line entry point's
156+
-- | ES module static loading phase.
92157
-- |
93-
-- | The argument to the callback is the exit code which the process is about
94-
-- | to exit with.
95-
foreign import onExit :: (Int -> Effect Unit) -> Effect Unit
96-
97-
-- | Install a handler for uncaught exceptions. The callback will be called
98-
-- | when the process emits the `uncaughtException` event. The handler
99-
-- | currently does not expose the second `origin` argument from the Node 12
100-
-- | version of this event to maintain compatibility with older Node versions.
101-
foreign import onUncaughtException :: (Error -> Effect Unit) -> Effect Unit
102-
103-
-- | Install a handler for unhandled promise rejections. The callback will be
104-
-- | called when the process emits the `unhandledRejection` event.
158+
-- | The 'uncaughtException' event is emitted when an uncaught JavaScript exception bubbles
159+
-- | all the way back to the event loop. By default, Node.js handles such exceptions
160+
-- | by printing the stack trace to `stderr` and exiting with code 1,
161+
-- | overriding any previously set `process.exitCode`.
162+
-- | Adding a handler for the 'uncaughtException' event overrides this default behavior.
163+
-- | Alternatively, change the process.exitCode in the 'uncaughtException' handler which will
164+
-- | result in the process exiting with the provided exit code. Otherwise, in the presence of
165+
-- | such handler the process will exit with 0.
105166
-- |
106-
-- | The first argument to the handler can be whatever type the unhandled
107-
-- | Promise yielded on rejection (typically, but not necessarily, an `Error`).
167+
-- | 'uncaughtException' is a crude mechanism for exception handling intended to be used only as a last resort. The event should not be used as an equivalent to On Error Resume Next. Unhandled exceptions inherently mean that an application is in an undefined state. Attempting to resume application code without properly recovering from the exception can cause additional unforeseen and unpredictable issues.
168+
-- |
169+
-- | Exceptions thrown from within the event handler will not be caught. Instead the process will exit with a non-zero exit code and the stack trace will be printed. This is to avoid infinite recursion.
170+
-- |
171+
-- | Attempting to resume normally after an uncaught exception can be similar to pulling out the power cord when upgrading a computer. Nine out of ten times, nothing happens. But the tenth time, the system becomes corrupted.
172+
-- |
173+
-- | The correct use of 'uncaughtException' is to perform synchronous cleanup of allocated resources (e.g. file descriptors, handles, etc) before shutting down the process. It is not safe to resume normal operation after 'uncaughtException'.
174+
-- |
175+
-- | To restart a crashed application in a more reliable way, whether 'uncaughtException' is emitted or not, an external monitor should be employed in a separate process to detect application failures and recover or restart as needed.
176+
uncaughtExceptionH :: EventHandle2 Process Error String
177+
uncaughtExceptionH = EventHandle "uncaughtException" \cb -> mkEffectFn2 \a b -> cb a b
178+
179+
-- | Args:
180+
-- | - `reason` <Error> | <any> The object with which the promise was rejected (typically an Error object).
181+
-- | - `promise` <Promise> The rejected promise.
108182
-- |
109-
-- | The handler currently does not expose the type of the second argument,
110-
-- | which is a `Promise`, in order to allow users of this library to choose
111-
-- | their own PureScript `Promise` bindings.
112-
foreign import onUnhandledRejection :: forall a b. (a -> b -> Effect Unit) -> Effect Unit
183+
-- | The 'unhandledRejection' event is emitted whenever a Promise is rejected and no error handler is attached to the promise within a turn of the event loop. When programming with Promises, exceptions are encapsulated as "rejected promises". Rejections can be caught and handled using promise.catch() and are propagated through a Promise chain. The 'unhandledRejection' event is useful for detecting and keeping track of promises that were rejected whose rejections have not yet been handled.
184+
unhandledRejectionH :: EventHandle2 Process Foreign Foreign
185+
unhandledRejectionH = EventHandle "unhandledRejection" \cb -> mkEffectFn2 \a b -> cb a b
186+
187+
-- | Args:
188+
-- | - `warning` <Error> Key properties of the warning are:
189+
-- | - `name` <string> The name of the warning. Default: 'Warning'.
190+
-- | - `message` <string> A system-provided description of the warning.
191+
-- | - `stack` <string> A stack trace to the location in the code where the warning was issued.
192+
-- |
193+
-- | The 'warning' event is emitted whenever Node.js emits a process warning.
194+
-- |
195+
-- | A process warning is similar to an error in that it describes exceptional conditions that are being brought to the user's attention. However, warnings are not part of the normal Node.js and JavaScript error handling flow. Node.js can emit warnings whenever it detects bad coding practices that could lead to sub-optimal application performance, bugs, or security vulnerabilities.
196+
-- | By default, Node.js will print process warnings to stderr. The --no-warnings command-line option can be used to suppress the default console output but the 'warning' event will still be emitted by the process object.
197+
warningH :: EventHandle1 Process Error
198+
warningH = EventHandle "warning" mkEffectFn1
113199

114-
foreign import onSignalImpl :: String -> Effect Unit -> Effect Unit
200+
-- | Args:
201+
-- | - `worker` <Worker> The <Worker> that was created.
202+
-- |
203+
-- | The 'worker' event is emitted after a new <Worker> thread has been created.
204+
workerH :: EventHandle1 Process Foreign
205+
workerH = EventHandle "worker" mkEffectFn1
115206

116-
-- | Install a handler for a particular signal.
117-
onSignal :: Signal -> Effect Unit -> Effect Unit
118-
onSignal sig = onSignalImpl (Signal.toString sig)
207+
-- | Rather than support an `EventHandle` for every possible `Signal`,
208+
-- | this function provides one a convenient way for constructing one for any given signal.
209+
-- |
210+
-- | See Node docs: https://nodejs.org/dist/latest-v18.x/docs/api/process.html#signal-events
211+
mkSignalH :: Signal -> EventHandle Process (Effect Unit) (Effect Unit)
212+
mkSignalH sig = EventHandle (Signal.toString sig) identity
213+
214+
-- | Same as `mkSignalH` but allows for more options in case the `Signal` ADT is missing any.
215+
-- |
216+
-- | See Node docs: https://nodejs.org/dist/latest-v18.x/docs/api/process.html#signal-events
217+
mkSignalH' :: String -> EventHandle Process (Effect Unit) (Effect Unit)
218+
mkSignalH' sig = EventHandle (String.toUpper sig) identity
119219

120220
-- | The `process.abort()` method causes the Node.js process to exit immediately and generate a core file.
121221
-- | This feature is not available in Worker threads.

0 commit comments

Comments
 (0)