Timing out
On this page
In the world of programming, we often deal with tasks that take some time to complete. Sometimes, we want to set a limit on how long we are willing to wait for a task to finish. This is where the Effect.timeout
function comes into play. It allows us to put a time constraint on an operation, ensuring that it doesn't run indefinitely. In this guide, we'll explore how to use Effect.timeout
effectively.
timeout
Effect lets us timeout any effect using the Effect.timeout
function. It returns a new effect with the following characteristics:
- If an error is returned, it signifies that the timeout expired before the completion of the effect's execution.
- If successful, it indicates that the effect finished within the specified timeout, and the result of the effect is included.
If an effect times out, then instead of continuing to execute in the background, it will be interrupted so no resources will be wasted.
Suppose we have the following effect:
ts
import {Effect } from "effect"constprogram =Effect .gen (function* (_ ) {console .log ("start doing something...")yield*_ (Effect .sleep ("2 seconds"))console .log ("my job is finished!")return "some result"})constmain =program .pipe (Effect .timeout ("3 seconds"))
ts
import {Effect } from "effect"constprogram =Effect .gen (function* (_ ) {console .log ("start doing something...")yield*_ (Effect .sleep ("2 seconds"))console .log ("my job is finished!")return "some result"})constmain =program .pipe (Effect .timeout ("3 seconds"))
When we apply Effect.timeout
to program
, it behaves in one of the following ways:
-
If the original effect (
program
in this case) completes before the timeout elapses, it returns the produced value by the original effect. Here's an example:tsimport {Effect } from "effect"constprogram =Effect .gen (function* (_ ) {console .log ("start doing something...")yield*_ (Effect .sleep ("2 seconds"))console .log ("my job is finished!")return "some result"})constmain =program .pipe (Effect .timeout ("3 seconds"))Effect .runPromise (main ).then (console .log ,console .error )/*Output:start doing something...my job is finished!some result*/tsimport {Effect } from "effect"constprogram =Effect .gen (function* (_ ) {console .log ("start doing something...")yield*_ (Effect .sleep ("2 seconds"))console .log ("my job is finished!")return "some result"})constmain =program .pipe (Effect .timeout ("3 seconds"))Effect .runPromise (main ).then (console .log ,console .error )/*Output:start doing something...my job is finished!some result*/ -
If the timeout elapses before the original effect completes, and the effect is interruptible, it will be immediately interrupted, and the timeout operation produces a
NoSuchElementException
error. Here's an example:tsimport {Effect } from "effect"constprogram =Effect .gen (function* (_ ) {console .log ("start doing something...")yield*_ (Effect .sleep ("2 seconds"))console .log ("my job is finished!")return "some result"})constmain =program .pipe (Effect .timeout ("1 seconds"))Effect .runPromise (main ).then (console .log ,console .error )/*Output:start doing something...{_id: 'FiberFailure',cause: {_id: 'Cause',_tag: 'Fail',failure: { _tag: 'NoSuchElementException' }}}*/tsimport {Effect } from "effect"constprogram =Effect .gen (function* (_ ) {console .log ("start doing something...")yield*_ (Effect .sleep ("2 seconds"))console .log ("my job is finished!")return "some result"})constmain =program .pipe (Effect .timeout ("1 seconds"))Effect .runPromise (main ).then (console .log ,console .error )/*Output:start doing something...{_id: 'FiberFailure',cause: {_id: 'Cause',_tag: 'Fail',failure: { _tag: 'NoSuchElementException' }}}*/ -
If the effect is uninterruptible, it will be blocked until the original effect safely finishes its work, and then the timeout operator produces a
NoSuchElementException
error. Here's an example:tsimport {Effect } from "effect"constprogram =Effect .gen (function* (_ ) {console .log ("start doing something...")yield*_ (Effect .sleep ("2 seconds"))console .log ("my job is finished!")return "some result"})constmain =program .pipe (Effect .uninterruptible ,Effect .timeout ("1 seconds"))Effect .runPromise (main ).then (console .log ,console .error )/*Output:start doing something...my job is finished!{_id: 'FiberFailure',cause: {_id: 'Cause',_tag: 'Fail',failure: { _tag: 'NoSuchElementException' }}}*/tsimport {Effect } from "effect"constprogram =Effect .gen (function* (_ ) {console .log ("start doing something...")yield*_ (Effect .sleep ("2 seconds"))console .log ("my job is finished!")return "some result"})constmain =program .pipe (Effect .uninterruptible ,Effect .timeout ("1 seconds"))Effect .runPromise (main ).then (console .log ,console .error )/*Output:start doing something...my job is finished!{_id: 'FiberFailure',cause: {_id: 'Cause',_tag: 'Fail',failure: { _tag: 'NoSuchElementException' }}}*/If we want to return early after the timeout has passed and before an underlying effect has been interrupted, we can use
Effect.disconnect
. This technique allows the original effect to be interrupted in the background. Here's an example:tsimport {Effect } from "effect"constprogram =Effect .gen (function* (_ ) {console .log ("start doing something...")yield*_ (Effect .sleep ("2 seconds"))console .log ("my job is finished!")return "some result"})constmain =program .pipe (Effect .uninterruptible ,Effect .disconnect ,Effect .timeout ("1 seconds"))Effect .runPromise (main ).then (console .log ,console .error )/*Output:start doing something...{_id: 'FiberFailure',cause: {_id: 'Cause',_tag: 'Fail',failure: { _tag: 'NoSuchElementException' }}}my job is finished!*/tsimport {Effect } from "effect"constprogram =Effect .gen (function* (_ ) {console .log ("start doing something...")yield*_ (Effect .sleep ("2 seconds"))console .log ("my job is finished!")return "some result"})constmain =program .pipe (Effect .uninterruptible ,Effect .disconnect ,Effect .timeout ("1 seconds"))Effect .runPromise (main ).then (console .log ,console .error )/*Output:start doing something...{_id: 'FiberFailure',cause: {_id: 'Cause',_tag: 'Fail',failure: { _tag: 'NoSuchElementException' }}}my job is finished!*/
Customizing Timeout Behavior
In addition to the basic Effect.timeout
function, there are variations available that allow you to customize the behavior when a timeout occurs.
timeoutTo
The timeoutTo
function is similar to Effect.timeout
, but it provides more control over the final result type. It allows you to specify what should be returned in case of a timeout. Here's an example:
ts
import {Effect ,Either } from "effect"constprogram =Effect .gen (function* (_ ) {console .log ("start doing something...")yield*_ (Effect .sleep ("2 seconds"))console .log ("my job is finished!")return "some result"})constmain =program .pipe (Effect .timeoutTo ({duration : "1 seconds",// let's return an EitheronSuccess : (result ):Either .Either <string, string> =>Either .right (result ),onTimeout : ():Either .Either <string, string> =>Either .left ("timeout!")}))Effect .runPromise (main ).then (console .log )/*Output:start doing something...{_id: "Either",_tag: "Left",left: "timeout!"}*/
ts
import {Effect ,Either } from "effect"constprogram =Effect .gen (function* (_ ) {console .log ("start doing something...")yield*_ (Effect .sleep ("2 seconds"))console .log ("my job is finished!")return "some result"})constmain =program .pipe (Effect .timeoutTo ({duration : "1 seconds",// let's return an EitheronSuccess : (result ):Either .Either <string, string> =>Either .right (result ),onTimeout : ():Either .Either <string, string> =>Either .left ("timeout!")}))Effect .runPromise (main ).then (console .log )/*Output:start doing something...{_id: "Either",_tag: "Left",left: "timeout!"}*/
timeoutFail
The timeoutFail
function allows you to produce a specific error when a timeout happens. This can be helpful for signaling timeout errors in your code. Here's an example:
ts
import {Effect } from "effect"constprogram =Effect .gen (function* (_ ) {console .log ("start doing something...")yield*_ (Effect .sleep ("2 seconds"))console .log ("my job is finished!")return "some result"})constmain =program .pipe (Effect .timeoutFail ({duration : "1 seconds",onTimeout : () => newError ("timeout")}))Effect .runPromise (main ).then (console .log ,console .error )/*Output:start doing something...{_id: 'FiberFailure',cause: {_id: 'Cause',_tag: 'Fail',failure: Error: timeout... stack trace ...}}*/
ts
import {Effect } from "effect"constprogram =Effect .gen (function* (_ ) {console .log ("start doing something...")yield*_ (Effect .sleep ("2 seconds"))console .log ("my job is finished!")return "some result"})constmain =program .pipe (Effect .timeoutFail ({duration : "1 seconds",onTimeout : () => newError ("timeout")}))Effect .runPromise (main ).then (console .log ,console .error )/*Output:start doing something...{_id: 'FiberFailure',cause: {_id: 'Cause',_tag: 'Fail',failure: Error: timeout... stack trace ...}}*/
timeoutFailCause
The timeoutFailCause
function allows you to produce a specific defect when a timeout occurs. This is useful when you need to handle timeouts as exceptional cases in your code. Here's an example:
ts
import {Effect ,Cause } from "effect"constprogram =Effect .gen (function* (_ ) {console .log ("start doing something...")yield*_ (Effect .sleep ("2 seconds"))console .log ("my job is finished!")return "some result"})constmain =program .pipe (Effect .timeoutFailCause ({duration : "1 seconds",onTimeout : () =>Cause .die ("timeout")}))Effect .runPromiseExit (main ).then (console .log )/*Output:start doing something...{_id: 'Exit',_tag: 'Failure',cause: { _id: 'Cause', _tag: 'Die', defect: 'timeout' }}*/
ts
import {Effect ,Cause } from "effect"constprogram =Effect .gen (function* (_ ) {console .log ("start doing something...")yield*_ (Effect .sleep ("2 seconds"))console .log ("my job is finished!")return "some result"})constmain =program .pipe (Effect .timeoutFailCause ({duration : "1 seconds",onTimeout : () =>Cause .die ("timeout")}))Effect .runPromiseExit (main ).then (console .log )/*Output:start doing something...{_id: 'Exit',_tag: 'Failure',cause: { _id: 'Cause', _tag: 'Die', defect: 'timeout' }}*/