{"id":28646721,"url":"https://github.com/igrishaev/whew","last_synced_at":"2025-06-13T02:06:40.843Z","repository":{"id":291126768,"uuid":"976667229","full_name":"igrishaev/whew","owner":"igrishaev","description":null,"archived":false,"fork":false,"pushed_at":"2025-05-08T15:11:26.000Z","size":33,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-05-08T16:26:04.634Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/igrishaev.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-05-02T14:22:39.000Z","updated_at":"2025-05-08T15:11:29.000Z","dependencies_parsed_at":"2025-05-08T16:26:07.316Z","dependency_job_id":null,"html_url":"https://github.com/igrishaev/whew","commit_stats":null,"previous_names":["igrishaev/foobar","igrishaev/whew"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/igrishaev/whew","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/igrishaev%2Fwhew","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/igrishaev%2Fwhew/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/igrishaev%2Fwhew/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/igrishaev%2Fwhew/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/igrishaev","download_url":"https://codeload.github.com/igrishaev/whew/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/igrishaev%2Fwhew/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":259565562,"owners_count":22877347,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-06-13T02:06:40.252Z","updated_at":"2025-06-13T02:06:40.800Z","avatar_url":"https://github.com/igrishaev.png","language":"Clojure","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Whew\n\nA zero-deps library that wraps Java's `CompletableFuture` class. Provides\nfunctions and macros to drag futures through handlers, looping over them,\nmapping and so on. Deals with nested futures (when a future returns a future and\nso on).\n\n[manifold]: https://github.com/clj-commons/manifold\n[auspex]: https://github.com/mpenet/auspex\n\nThe library might remind you [Manifold][manifold] or [Auspex][auspex]. But the\nAPI is a bit different and it handles some special cases.\n\n\u003c!-- toc --\u003e\n\n- [Installation](#installation)\n- [Usage](#usage)\n- [API](#api)\n  * [Creating Futures](#creating-futures)\n  * [On Executors](#on-executors)\n  * [Chaining Futures](#chaining-futures)\n  * [Dereferencing](#dereferencing)\n  * [Folding](#folding)\n  * [Zipping, Any-of, One-of](#zipping-any-of-one-of)\n  * [The Let Macro](#the-let-macro)\n  * [For \u0026 Map](#for--map)\n  * [Loop \u0026 Recur](#loop--recur)\n  * [Timeout \u0026 Cancelling](#timeout--cancelling)\n- [Misc](#misc)\n\n\u003c!-- tocstop --\u003e\n\n## Installation\n\nRequires Java version at least 8. Add a dependency:\n\n~~~clojure\n;; lein\n[com.github.igrishaev/whew \"0.1.1\"]\n\n;; deps\ncom.github.igrishaev/whew {:mvn/version \"0.1.1\"}\n~~~\n\n## Usage\n\nImport the library:\n\n~~~clojure\n(ns org.some.project\n  (:require\n    [whew.core :as $]))\n~~~\n\nWhew provides functions and macros named after their Clojure counterparts,\ne.g. `map`, `future`, `loop`, etc. Never `:use` this library but rather\n`:require` it using an alias. Here and below we will use `$`.\n\nA quick demo. Let's prepare a function that makes some IO, for example fetches\nJSON from network:\n\n~~~clojure\n(defn get-json [code]\n  (-\u003e (format \"https://http.dog/%d.json\" code)\n      (http/get {:as :json\n                 :throw-exceptions true})\n      :body))\n~~~\n\nHere is how you run it as a future:\n\n~~~clojure\n(def -f ($/future\n          (get-json 101)))\n~~~\n\nThe `future` macro accepts arbitrary block of code and wraps it into a future\nthat gets executed in the background. Now deref it, and you'll get a result:\n\n~~~clojure\n@-f\n\n{:image\n {:avif \"https://http.dog/101.avif\"\n  :jpg \"https://http.dog/101.jpg\"\n  :jxl \"https://http.dog/101.jxl\"\n  :webp \"https://http.dog/101.webp\"}\n :status_code 101\n :title \"Switching Protocols\"\n :url \"https://http.dog/101\"}\n~~~\n\nBy derefing a future, you freeze the current thread forcing it to wait until the\nfuture is ready. But you can assign a post-processing handler which gets run in\nthe background with a result of a future. Adding a handler returns a new\nfuture. Below, we compose a bit of HTML markup out from a fetched data:\n\n~~~clojure\n(-\u003e -f\n    ($/then [data]\n      (-\u003e data :image :jpg))\n    ($/then [url]\n      [:a {:src url}\n       \"Click me!\"])\n    (deref))\n\n[:a {:src \"https://http.dog/101.jpg\"} \"Click me!\"]\n~~~\n\nEach `then` handler accepts a future and a binding symbol. It is bound to a\nresult of a previous future. The block of code can return either a flat value or\na future as well:\n\n~~~clojure\n(-\u003e ($/future\n      (get-json 201))\n    ($/then [data]\n      ($/future\n        (do-something-else (:url data))))\n    ($/then [response]\n      ...))\n~~~\n\nYou can enqueue as many `then` handles as you want.\n\nShould any of them throw an exception, a future becomes failed, and no more\nfurther handlers apply. If you `deref` such a failed future, you'll get an\nexception. To recover from it, there is another `catch` handler:\n\n~~~clojure\n(-\u003e ($/future\n      (get-json 333)) ;; weird code\n    ($/catch [e]\n      {:error true\n       :message (ex-message e)})\n    (deref))\n\n{:error true, :message \"clj-http: status 404\"}\n~~~\n\nThe `e` symbol is bound to an exception value occurred before. What's important,\nit will be an **unwrapped exception**! By default, the `CompletableFuture` class\nwraps any runtime exception into various classes like `ExecutionException` or\n`CompletionException`. If you branch your logic depending on exception class or\ninheritance, you'll need to get an `ex-cause` first. But the `catch` macro\nhandles it for you: the `e` variable will be of the right class.\n\nWhew provides a number of various macros to express logic throughout futures:\nmixing and chaining them, zipping, waiting multiple futures for completion and so\non. The section below describes these in detail.\n\n## API\n\n### Creating Futures\n\nBoth `future` and `future-async` macros take an arbitrary block of code and\nreturn a future that carries a result of this block:\n\n~~~clojure\n($/future 42)\n\n($/future-async\n  (let [...]\n    (do-long-job ...)))\n~~~\n\nA future might return a future which returns a future which returs a future and\nso on:\n\n~~~clojure\n($/future\n  ($/future\n    ($/future\n      ($/future 42))))\n~~~\n\nTo handle the result in a standard way, you'll have to deref it four times:\n\n~~~clojure\n@@@@($/future\n      ($/future\n        ($/future\n          ($/future 42))))\n~~~\n\nBut Whew handles such cases:\n\n~~~clojure\n(-\u003e ($/future\n      ($/future\n        ($/future\n          ($/future 42))))\n    ($/then [x] (inc x)) ;; x = 42\n    (deref))\n43\n~~~\n\nThere is a special `deref` function that takes folding into account:\n\n~~~clojure\n(-\u003e ($/future\n      ($/future\n        ($/future\n          ($/future 42))))\n    ($/deref))\n42\n~~~\n\nUsually you don't need to deref futures explicitly neither with the standard\n`deref` nor the custom `$/deref`.\n\nThe `future-sync` macro takes a block of code but executes it immediately in the\nsame thread and produces a **completed** future. When a future is completed, it\nmeans it can be `deref`-fed right now without delay. This is useful when you\nwant just to mimic a future.\n\nThe block of code is implicitly wrapped with try/catch. Should an exception pop\nup, the result will be a failed future with this exception:\n\nThe following piece of code will throw immediately:\n\n~~~clojure\n(def -f ($/future-sync (/ 0 0)))\n\n($/failed? -f)\ntrue\n\n@-f\n;; Execution error (ArithmeticException)...\n;; Divide by zero\n~~~\n\nThe `-\u003efuture` function turns any value into a completed future. Any `Throwable`\ninstance produces a failed future: the one than cannot be propagated through\n`then` handlers, but only `catch`.\n\n~~~clojure\n(-\u003e ($/-\u003efuture 1)\n      ($/then [x]\n        (inc x))\n      ($/deref))\n\n;; 2\n\n(-\u003e ($/-\u003efuture (ex-info \"boom\" {:a 1}))\n    ($/then [x]\n      (inc x))\n    ($/catch [e]\n      {:data (ex-data e)})\n    ($/deref))\n\n;; {:data {:a 1}}\n~~~\n\nThe `future?` predicate checks if a g value is a future:\n\n~~~clojure\n($/future? ($/future 1))\ntrue\n\n($/future? 1)\nfalse\n~~~\n\nThe `failed?` predicated checks if a future has failed. Pay attention that in\nthe beginning, we don't know it untill it really has:\n\n~~~clojure\n(def -f ($/future\n          (Thread/sleep 5000)\n          (/ 0 0)))\n\n($/failed? -f)\nfalse\n\n;; wait for 5 seconds\n\n($/failed? -f)\ntrue\n~~~\n\n### On Executors\n\nBy default, the `CompletableFuture` class relies on the\n`ForkJoinPool/commonPool` executor although it's possible to override it. By\nrunning benchmarks, I noticed that the standard\n`clojure.core.Agent/soloExecutor` used for built-in Clojure futures and agents\nis more robust. Thus, when you spawn futures using `($/future)` and\n`($/future-async)` macros, the `soloExecutor` executor is used.\n\nThe `future-via` macro acts like `future-async` but accepts a custom `Executor`\ninstance to work with. Any further `then` and `catch` handlers will be served\nunder the same executor as well. Here is an example of using a custom\ntwo-threaded executor:\n\n~~~clojure\n(with-open [executor\n            (Executors/newFixedThreadPool 2)]\n  (-\u003e ($/future-via [executor]\n        (let [a 1 b 2]\n          (+ a b)))\n      ($/then [x]\n        ...)))\n~~~\n\nYou can also use a virtual executor that relies on virtual threads available\nsince Java 21:\n\n~~~clojure\n(with-open [e (Executors/newVirtualThreadPerTaskExecutor)]\n  ($/future-via [a]\n    ...))\n~~~\n\nThe default executor which Whew relies on is stored in the\n`whew.core/EXECUTOR_DEFAULT` variable, and it's initial value is\n`Agent/soloExecutor`. You can switch it globally to something else as follows:\n\n~~~clojure\n($/set-executor! some-executor)\n~~~\n\nAt the moment, Whew provides the following `Executor` constants:\n\n- `$/EXECUTOR_CLJ_SOLO`: a built-it `Agent/soloExecutor` Clojure executor;\n- `$/EXECUTOR_CLJ_POOLED`: a built-it `Agent/pooledExecutor` Clojure executor;\n- `$/EXECUTOR_FJ_COMMON`: a default ForkJoin common pool.\n\n### Chaining Futures\n\n`Then` macro provides a new future based on the previous one:\n\n~~~clojure\n@(-\u003e ($/future 1) ($/then [x] (inc x)))\n2\n~~~\n\nIt works with non-future values as well. Internally, they get transformed into a\ncompleted future:\n\n~~~clojure\n@(-\u003e 1 ($/then [x] (inc x)))\n2\n~~~\n\nA macro `then-fn` acts the same but accepts a function which gets applied to a\nresult of a previous future:\n\n~~~clojure\n@(-\u003e 1 ($/then-fn inc))\n2\n~~~\n\nYou can pass additional arguments too:\n\n~~~clojure\n@(-\u003e 1 ($/then-fn + 100))\n101\n~~~\n\nThe `catch` macro handles an exception occurred beforehand. Pay attention that\nthe second `inc` form didn't work because it doesn't apply to a failed future:\n\n~~~clojure\n@(-\u003e ($/future 1)\n     ($/then-fn inc) ;; works\n     ($/then-fn / 0) ;; fails\n     ($/then-fn inc) ;; unreached\n     ($/catch [e]    ;; recovered\n       :this-is-fine))\n\n:this-is-fine\n~~~\n\nAs it was mentioned above, the macro unwraps exceptions. The `e` variable will\nbe an instance of `ArithmeticException` but not `ExecutionException`.\n\nThe `catch-fn` macro acts the same but accepts 1-argument function that handles\nan exception:\n\n~~~clojure\n@(-\u003e ($/future 1)\n     ($/then [x]\n       (throw (ex-info \"boom\" {:foo 1})))\n     ($/catch-fn ex-data)\n     ($/then [data]\n        {:ex-data data}))\n\n{:ex-data {:foo 1}}\n~~~\n\nIt accepts additional arguments like `then-fn` does but usually they're not\nneeded.\n\nThe `handle` macro captures both a result and an exception as a pair:\n\n~~~clojure\n@(-\u003e ($/future 1)\n     ($/handle [r e]\n       {:result r :exception e}))\n\n{:result 1 :exception nil}\n~~~\n\nUsually you check if an exception is nil to decide the logic. The `handle-fn`\nmacro is similar but accepts a 2-arity function which handles both a result and\nan exception:\n\n~~~clojure\n@(-\u003e ($/future 1)\n     ($/handle-fn\n       (fn [r e]\n         {:result r :exception e})))\n\n{:result 1 :exception nil}\n~~~\n\nIn both cases, exceptions are unwrapped.\n\n### Dereferencing\n\nThe standard `@` operator and the `deref` function get a value from a future but\ndon't take multiple levels into account:\n\n~~~clojure\n@($/future\n   ($/future\n     ($/future\n       ($/future 1))))\n\n#object[...CompletableFuture 0x287238e4 \"...[Completed normally]\"]\n~~~\n\nSo you've to go to the end:\n\n~~~clojure\n@@@@($/future\n     ($/future\n       ($/future\n         ($/future 1))))\n1\n~~~\n\nBut the `$/deref` function from Whew does the same with no issues:\n\n~~~clojure\n($/deref\n  ($/future\n    ($/future\n      ($/future\n        ($/future 1)))))\n1\n~~~\n\nTo not hang forever, it accepts an amount of milliseconds (how long to wait) and\na default value to return on timeout:\n\n~~~clojure\n($/deref ($/future\n           (Thread/sleep 1000)\n           :done)\n         100\n         :too-long)\n:too-long\n~~~\n\n### Folding\n\nFolding a future means removing its unnecessarily levels, for example:\n\n~~~clojure\n;; this\n($/future ($/future ($/future 1)))\n\n;; becomes this\n($/future 1)\n~~~\n\nWhen a future is folded, only one `deref` is required to obtain a value. The\n`fold` function does it:\n\n~~~clojure\n@($/fold ($/future ($/future ($/future 1))))\n;; 1\n~~~\n\nThe `deref` function described above is nothing but a combo of `fold` and `.get`\ninvocations.\n\nUsually you don't need to fold futures manually because `then`, `catch` and\nother macros do it for you.\n\n### Zipping, Any-of, One-of\n\nThe `zip` macro accepts a number of forms. Each form is turned into an async\nfuture. The result is a future that gets completed when all the futures complete\n(either successfully or with an exception). It gets a vector of plain values:\n\n~~~clojure\n(-\u003e ($/zip 1 ($/future ($/future 2)) 3)\n    ($/then [vs]\n      {:values vs})\n    (deref))\n\n{:values [1 2 3]}\n~~~\n\nShould any future fail, so the entire future does with the same exception:\n\n~~~clojure\n(-\u003e ($/zip 1 ($/future ($/future (/ 0 0))) 3)\n    ($/then [vs]\n      {:values vs})\n    ($/catch [e]\n      {:error (ex-message e)})\n    (deref))\n\n{:error \"Divide by zero\"}\n~~~\n\nThe `all-of` function acts the same: it accepts a collection of futures and\nreturns a future with completed items:\n\n~~~clojure\n(-\u003e ($/all-of [1 ($/future ($/future 2)) 3])\n    ($/then [vs]\n      {:values vs})\n    (deref))\n\n{:values [1 2 3]}\n~~~\n\nThe difference is, `all-of` is a function but not a macro. It's useful when you\nhave a collection of futures produced by other functions.\n\nThe `any-of` function takes a collection of futures and waits for the first\ncompleted one. The result is a future that carries a value of this future:\n\n~~~clojure\n@($/any-of [($/future\n              (Thread/sleep 300)\n              :A)\n            ($/future\n              (Thread/sleep 200)\n              :B)\n            ($/future\n              (Thread/sleep 100)\n              :C)])\n\n;; C\n~~~\n\nThe result is `:C` because the third future took less time to complete.\n\n### The Let Macro\n\nThe `let` macro mimics the one from the standard Clojure library, but:\n\n- any value (in the right binding part) can return a future;\n- bindings must not depend on each other;\n- the body is executed when all the futures are completed;\n- the body has access to derefed values but not futures.\n\nHere is a small demo. We use the `get-json` function that fetches a piece of\ndata by a numeric code.\n\n~~~clojure\n@($/let [resp-101 ($/future (get-json 101))\n         resp-202 ($/future (get-json 202))\n         resp-404 ($/future (get-json 404))]\n   {101 resp-101\n    202 resp-202\n    404 resp-404})\n\n{101\n {:image\n  {:jpg \"https://http.dog/101.jpg\"}\n  :title \"Switching Protocols\"\n  :url \"https://http.dog/101\"}\n 202\n {:image\n  {:jpg \"https://http.dog/202.jpg\"}\n  :title \"Accepted\"\n  :url \"https://http.dog/202\"}\n 404\n {:image\n  {:jpg \"https://http.dog/404.jpg\"}\n  :title \"Not Found\"\n  :url \"https://http.dog/404\"}}\n~~~\n\nWhat's important, all three `get-json` invocations are done in parallel. When\nevery of them is ready, the body gets access to their values.\n\nShould any binding fail, the entire `let` node fails as well:\n\n~~~clojure\n@(-\u003e ($/let [resp-101 ($/future (get-json 101))\n             resp-321 ($/future (get-json 321)) ;; wrong code\n             resp-404 ($/future (get-json 404))]\n       {101 resp-101\n        321 resp-321\n        404 resp-404})\n     ($/catch [e]\n       (-\u003e e\n           ex-data\n           (select-keys [:status :reason-phrase]))))\n\n{:status 404 :reason-phrase \"Not Found\"}\n~~~\n\nIf you unsure about a certain binding, protect it with a `catch` macro:\n\n~~~clojure\n@(-\u003e ($/let [resp-101 ($/future (get-json 101))\n             resp-321 (-\u003e ($/future (get-json 321))\n                          ($/catch [e]\n                            {:error true\n                             :code 321}))\n             resp-404 ($/future (get-json 404))]\n       {101 resp-101\n        321 resp-321\n        404 resp-404}))\n\n{101\n {:title \"Switching Protocols\"\n  :url \"https://http.dog/101\"}\n 321 {:error true, :code 321}\n 404\n {:title \"Not Found\"\n  :url \"https://http.dog/404\"}}\n~~~\n\n### For \u0026 Map\n\nThe `for` macro acts like the standard `for` but wraps each body expression into\na future. The result is a future holding all completed values. Here is how we\ncollect responses for given codes:\n\n~~~clojure\n@($/for [code [100 101 200 201 202 500]]\n   (get-json code))\n\n[{...} {...} {...} {...} {...} {...}]\n~~~\n\nShould any body expression fail, the entire future fails as well. Use the\n`catch` macro to handle an exception.\n\nThe macro supports special `:let`, `:when` and other options like the standart\n`for` does:\n\n~~~clojure\n@($/for [code [100 101 200 201 202 500]\n         :when (\u003e= code 500)]\n   (get-json code))\n\n[{:status_code 500}]\n~~~\n\n### Loop \u0026 Recur\n\nSometimes you need a future that returns a future that returs... and so on until\nsomething happens. This is where the `loop` macro helps. It reminds the standard\n`loop/recur` combo but has the following features:\n\n- it returns a future that is executed in the background;\n- use a special `$/recur` form but not the standard `recur` from `clojure.core`;\n- the body can produce a future;\n- bindings can be futures as well.\n\nThe example below fetches JSON data one by one. Every time a future gets\ncompleted, it performs the same block of code using bindings passed through the\n`$/recur` form.\n\n~~~clojure\n@($/loop [codes [100 101 200 201 202 500]\n          acc []]\n   (if-let [code (first codes)]\n     (let [result (get-json code)]\n       (println (format \"result for status %d\" code))\n       ($/recur (next codes) (conj acc result)))\n     acc))\n\n;; result for status 100\n;; result for status 101\n;; result for status 200\n;; result for status 201\n;; result for status 202\n;; result for status 500\n\n[{...} {...} ...]\n~~~\n\nThe `loop` macro is used rarely with futures because other facilities are\nusually enough. `Loop` is needed when you don't have an entire dataset at once,\nand fetch it on the fly. A good example is pagination: you fetch data by chunks\nand accumulate until the result is empty. Thus, you cannot run multiple futures\nat once because you don't know for how long to proceed. This kind of fetching\ncan be expressed as follows:\n\n~~~clojure\n(def PAGE_SIZE 100)\n\n(-\u003e ($/loop [acc []\n             off 0]\n      (let [result (fetch-items {:offset off :size PAGE_SIZE})\n            items (-\u003e result :response :items)]\n        (if (seq items)\n          ($/recur (into acc items) (+ off PAGE_SIZE))\n          acc)))\n    ($/then [items]\n      (process-items items))\n    ($/catch [e]\n      (log/errorf e \"error: %s\" 42)\n      (report-error e)))\n~~~\n\n### Timeout \u0026 Cancelling\n\nAny future can be limited in time by two strateges. First, it fails with a\ntimeout exception, and it's up to you how to handle this. Second, you specify a\ndefault value for this future which comes into play on timeout.\n\nThe `timeout` macro has two bodies for both cases. The first form accepts a\nfuture and a number of milliseconds. It assigns a timeout to a future. The\nfollowing example will fail because the sleep time is longer than the timeout:\n\n~~~clojure\n@(-\u003e ($/future\n       (Thread/sleep 1000))\n     ($/timeout 100)\n     ($/catch [e]\n       {:error (type e)}))\n\n{:error java.util.concurrent.TimeoutException}\n~~~\n\nPay attention that the `TimeoutException` instance has no message: the\n`(ex-message e)` invocation will return nil.\n\nThe macro accepts an arbitrary block of code. The result becomes a value for a\nfuture when it breaches timeout:\n\n~~~clojure\n@(-\u003e ($/future\n       (Thread/sleep 1000))\n     ($/timeout 100\n        (let [a 1 b 2]\n          (println \"recovering from timeout\")\n          {:some [\"other\" :value]}))\n     ($/catch [e]\n       {:error (type e)})\n     ($/then [x]\n        (println \"final handler\")\n        {:data x}))\n\n;; recovering from timeout\n;; final handler\n;; {:data {:some [\"other\" :value]}}\n~~~\n\nMost likely you don't need to set timeouts explicitly: modern HTTP clients allow\nto specify socket timings when making calls. The same applies to any libraries\nworking with sockets. But in rare cases, an explicit timeout might help.\n\nCanceling is something opposite to a timeout. It is when you ask to reject a\nfuture spawned previously. Canceling a completed future has no effect. But if it\nhas not been completed or failed before, a cancellation request completes a\nfuture with `CancellationException`. Later or, such a future can be checked for\ncancellation state with a predicate.\n\nThe `cancel` function tries to cancel a future. The result is a boolean value\nmeaning if the attempt was successful or not. Canceling a non-future value has\nno effect, and the result will be `nil`:\n\n~~~clojure\n(def -f ($/future 1))\n\n($/cancel -f)\nfalse ;; already completed\n~~~\n\nLet's try a slow future:\n\n~~~clojure\n(def -f ($/future\n          (Thread/sleep 5000)\n          (println \"DONE\")))\n\n($/cancel -f)\n\n($/cancelled? -f)\ntrue\n\n@-f ;; throws\n;; Execution error (CancellationException)\n~~~\n\nPay attention that you **will see** the `DONE` line printed! This is because a\nfuture was in the middle of processing, and the `CompletableFuture` class\ndoesn't interrupt calculation.\n\nIf you emit a cancellation request **before** a future has been started there\nwon't be any background cancellation. The following tests proves it:\n\n~~~clojure\n(let [p1 (promise)\n      p2 (promise)]\n  (with-open [e (Executors/newFixedThreadPool 1)]\n    (let [f1 ($/future-via [e]\n               (Thread/sleep 2000)\n               (println \"DONE 1\")\n               (deliver p1 true))\n          _ (Thread/sleep 500)\n          f2 ($/future-via [e]\n               (Thread/sleep 2000)\n               (println \"DONE 2\")\n               (deliver p2 true))]\n      (is ($/cancel f1))\n      (is ($/cancel f2))))\n  (is (realized? p1))\n  (is (not (realized? p2))))\n~~~\n\nAbove, we have an executor with one thread only. We spawn a future `f1` which\ntakes 2 seconds to complete, and wait for half of a second to let it start. The\nsecond future `f2` will stay in a queue until `f1` is done. Then we cancel both\nfutures. The `f1` accepts a cancellation request in the middle of processing. It\ngets canceled although the work is done: you'll see the \"DONE 1\" printing and\nthe `p1` promise will be delivered. But as `f2` has not been started, it gets\nremoved from a queue of the executor. You won't see \"DONE 2\", nor the `p2`\npromise will be delivered.\n\n## Misc\n\n~~~\n©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©\nIvan Grishaev, 2025. © UNLICENSE ©\n©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©\n~~~\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Figrishaev%2Fwhew","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Figrishaev%2Fwhew","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Figrishaev%2Fwhew/lists"}