(ns promesa-retry (:require ["util" :refer [format]] [promesa.core :as p :refer [reject! resolve!] :refer-macros [alet await]] [promesa.async-cljs :refer-macros [async]] [cljs.test :as t :include-macros true])) (defn backoff-duration [retries-count interval] (+ (* interval retries-count) (rand-int interval))) (defn- internal-retry [p f retries-left retries-count interval] (-> (f) (p/catch (fn [err] (js/console.error (format "Catch failure before retrying (%s retried left): %s" retries-left (.-message err))) (if (= 0 retries-left) (p/reject! p err) (let [d (backoff-duration retries-count interval)] (js/console.log (format "Will wait %s ms before retrying (%s retries left, tried %s times)" d retries-left retries-count)) (-> (p/delay d "Retry msg ") (p/then (fn [msg] (internal-retry p f (dec retries-left) (inc retries-count) interval)))))))) (p/catch (fn [err]));this catch is needed to get the promise returned by the the previous catch in case of a retry, we catch the error cause and do nothing with it (p/then (fn [return] (p/resolve! p return)))) p) (defn retry ([f retries-left] (retry f retries-left 1000)) ([f retries-left interval] (internal-retry (p/promise) f retries-left 1 interval))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Test ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (def counter (atom 0)) (defn dummy-function [] (async (if (< @counter 6) (do (println "Dummy function failed!") (swap! counter inc) (throw (ex-info "it failed !" {:counter @counter}))) (println "yo it works!")))) (t/deftest retry-test-success-in-the-end (reset! counter 0) (t/async done (-> (retry dummy-function 15 10) (p/catch (fn [err] (t/is err) (done))) (p/then (fn [] (js/console.log (format "Done retrying, success in the end")) (done)))))) (t/deftest retry-test-failure-in-the-end (reset! counter 0) (t/async done (-> (retry dummy-function 3 10) (p/catch (fn [err] (js/console.log "Done retrying, error in the end") (t/is err) (done))) (p/then (fn [] (js/console.log "Done retrying successfully") (done))) )))