4

Basically, what I want is to implement this piece of code in ClojureScript:

var win = window.open('foo.html', 'windowName');   
var timer = setInterval(function() {   
    if(win.closed) {  
        clearInterval(timer);  
        alert('closed');  
    }  
}, 1000);

I tried this:

(let [popup (.open js/window "foo.html" "windowName")
      interval (.setInterval
                js/window
                (fn []
                  (when (.-closed popup)
                    (do
                      ;; 'interval' is undefined at this point
                      (.clearInterval js/window interval)

                      (.alert js/window 'closed')))
                1000)]
...)

but CLJS compiler gives me a warning that interval is not defined.

Any ideas?

OlegTheCat
  • 4,443
  • 16
  • 24
  • 2
    Did you copy-pasted your code? There are typos in your code (`inteval` -> `interval`, `clearInteval` -> `clearInterval`) – Piotrek Bzdyl Apr 04 '16 at 12:06
  • sorry for this. anyway problem persists even if typos are fixed – OlegTheCat Apr 04 '16 at 12:14
  • 1
    To me, recursively calling setTimeout is often a better fit with functional programming. I'd definitely use setTimeout in the JavaScript version as well in this case. (I guess that doesn't really answer the question so I left it as a comment only.) – Kalle Jul 26 '16 at 07:53

3 Answers3

5

The issue is that you access interval local binding in your anonymous function before that binding has been defined (right hand side has to be evaluated first before it gets bound to interval symbol and until then interval is not defined.

You might workaround it by defining an atom storing your interval and access it from your callback function:

(let [popup (.open js/window 'foo.html', 'windowName')
      interval (atom nil)]
  (reset! interval (.setInterval
                    js/window
                    (fn []
                      (when (.-closed popup)
                        (do
                          (.clearInterval js/window @interval)
                          (.alert js/window "Closed")))))))

I am not sure if there is a more elegant way to achieve it using your approach with an interval callback.

Piotrek Bzdyl
  • 12,965
  • 1
  • 31
  • 49
4

Use of an atom

  • is easy
  • and is included within ClojureScript's core functionality

however interval being an atom may not clearly convey its actual nature and intent.
The original issue was the compiler's complaint that interval was not defined. So what is needed is a value that:

  • can be defined first and
  • resolved (delivered) later

That sounds like a promise to me. As of ClojureScript 1.8.34 Clojure promises aren't supported yet. Because of the existence of core.async there are indications that support of Clojure's promise isn't an urgent priority in ClojureScript - especially as core.async contains promise-chan. With promise-chan the code could be written as follows

(ns test.core
  (:require-macros [cljs.core.async.macros :refer [go]])
  (:require [cljs.core.async :refer [<! close! promise-chan put!]]))

(let [interval (promise-chan)
      fooWin (.open js/window "./foo.html", "windowName")
      checkOnFooWin 
        (fn []
          (when (.-closed fooWin) ;; when has an implicit do
            (go
              (.clearInterval js/window (<! interval)))
            (.alert js/window "Closed")))]
  (put! interval (.setInterval js/window checkOnFooWin 500))
  (close! interval))

This code is neither easier nor more elegant than the atom version - however it does have the advantage that

  • interval can only be resolved once
  • each and every value taken from interval will always be the initially delivered value.
Peer Reynders
  • 546
  • 3
  • 6
1

Another way is to use direct js interop:

(let [popup (.open js/window "foo.html" "windowName")]
  (js* "var interval = setInterval(function() {
          if (popup.closed) {
            clearInterval(interval);
            alert('close');
          }
        }, 500);")

    ...)
OlegTheCat
  • 4,443
  • 16
  • 24