Congratulations on running your first Effect!
However, in the previous section our program had the type void
in the
Success
channel of the effect, indicating that the program did not return
any meaningful value.
In this section, we are going to explore how we can create effects that do return meaningful values.
To create an effect that returns the same value every time that it is run, you
can use the Effect.succeed
constructor. It takes a pure value as input and
produces an effect that succeeds with that value.
Here's an example:
ts
Effect.succeed(42)
ts
Effect.succeed(42)
When run, this effect will always succeed with the value 42
.
If you want to create an effect that executes a synchronous function each time
it is run, you can use the Effect.sync
constructor. It takes a thunk (i.e. a
function with no arguments) as input, and calls that function each time the
effect is run. This is particularly useful when you need to encapsulate
operations within effect that have side-effects (e.g. logging to the console).
Here's an example:
ts
Effect.sync(() => Date.now())
ts
Effect.sync(() => Date.now())
This effect will return the current date each time. Note that if we used
Effect.succeed(Date.now())
instead, the date would be fixed to the time when
the effect was created instead of being re-evaluated each time the effect is
run.
The thunk passed to Effect.sync
should never throw errors.
If, despite warnings, the thunk passed to Effect.sync
does throw an error, the
Effect runtime will create an Effect
containing a "defect" (see
Unexpected Errors
in the documentation).
For situations where you need to perform a synchronous computation that may
fail, you can use the Effect.try
constructor. This constructor has two
variants that can be used depending upon whether or not you would like to
customize error handling.
For example, let's say we have a function numberOrFail
that can potentially
throw an Error
:
ts
function numberOrFail(num: number) {if (Math.random() > 0.5) {throw new Error("failed")}return num}
ts
function numberOrFail(num: number) {if (Math.random() > 0.5) {throw new Error("failed")}return num}
If we do not need to customize error handling at all, we can directly pass our
numberOrFail
function to Effect.try
. In this case, any errors thrown by the
function passed to Effect.try
will be wrapped by an UnknownException
:
ts
Effect.try(() => numberOrFail(42))
ts
Effect.try(() => numberOrFail(42))
However, if we do need to customize error handling, we can use the other
variant of Effect.try
which allows us to catch and handle any errors thrown
from our synchronous function:
ts
Effect.try({// Try to run our synchronous functiontry: () => numberOrFail(42),// Catch and re-map the error to something possibly more usefulcatch: (error) => new Error(`An error occurred: ${error}`)})
ts
Effect.try({// Try to run our synchronous functiontry: () => numberOrFail(42),// Catch and re-map the error to something possibly more usefulcatch: (error) => new Error(`An error occurred: ${error}`)})
This pattern is analagous to a standard try-catch block in JavaScript.
If you want to create an effect from an asynchronous function that returns a
Promise
, you can use the Effect.promise
constructor. The resulting effect
will resolve the returned Promise
and succeed with it's value.
Here's an example:
ts
Effect.promise(() => fetch("https://api.github.com/users/octocat"))
ts
Effect.promise(() => fetch("https://api.github.com/users/octocat"))
This effect will fetch the GitHub user octocat
and succeed with the response.
The Promise
returned by the thunk passed to Effect.promise
should never
reject.
If, despite warnings, the thunk passed to Effect.promise
does reject, the
Effect runtime will create an Effect
containing a "defect" (see
Unexpected Errors
in the documentation).
For situations where you need to perform an asynchronous computation that may
fail, you can use the Effect.tryPromise
constructor. Similar to Effect.try
,
the Effect.tryPromise
constructor has two variants that can be used depending
upon whether or not you would like to customize error handling.
If you do not need to customize error handling, the syntax is similar to
Effect.promise
, the difference being that if the promise rejects, the error
will be wrapped into an UnknownException
:
ts
Effect.tryPromise(() => fetch("https://api.github.com/users/octocat"))
ts
Effect.tryPromise(() => fetch("https://api.github.com/users/octocat"))
However, if you do need to customize error handling, you can use the other
variant of Effect.tryPromise
which allows for catching and handling the error
returned by the rejected Promise
:
ts
Effect.tryPromise({// Try to run the promisetry: () => fetch("https://api.github.com/users/octocat"),// Catch and re-map the error to something possibly more usefulcatch: (error) => new Error(`An error occurred: ${error}`)})
ts
Effect.tryPromise({// Try to run the promisetry: () => fetch("https://api.github.com/users/octocat"),// Catch and re-map the error to something possibly more usefulcatch: (error) => new Error(`An error occurred: ${error}`)})
This is similar to adding a .catch
handler to a Promise
.
Using the Effect functions we just learned, complete the TODO's in the editor.
Remember, if you ever get stuck try clicking the "Show Solution" button in the upper right-hand corner of the editor.