October 6, 2019
By: Kevin
clojureScript的WebBle初步尝试
web ble with cljs
本文不对webble本身做分析,仅仅进行了使用cljs调用webble接口的尝试,浏览器环境仅支持chrome。
JS版本的设备扫描
JS里promise用cljs实现,以web蓝牙为例:
以下代码来自webble官方demo
下面的代码可以扫描周围任意以字母或者数字开头的BLE服务。
function anyDeviceFilter() {
// This is the closest we can get for now to get all devices.
return Array.from('0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ')
.map(c => ({namePrefix: c}))
.concat({name: ''});
}
navigator.bluetooth.requestDevice({
filters: anyDeviceFilter(),
})
cljs版本的设备扫描
初步验证一下
;;下面这两种方式不work, 因为this指针的问题, bluetooth需要绑定到navigator上才行
;; https://stackoverflow.com/questions/31481396/uncaught-typeerror-illegal-invocation-when-trying-to-support-cross-browser-pr/31523066#31523066
;;(.-requestDevice (.-bluetooth js/navigator))
;;(.. js/navigator -bluetooth -requestDevice)
(def anyDeviceFilter
(mapv (fn [e] {"namePrefix" e})
"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"))
(js/navigator.bluetooth.requestDevice (clj->js {"filters" anyDeviceFilter}))
js的Promise链
仔细看一下代码,我们会发现Promise..then的频繁使用:
connectButton.addEventListener('click', function () {
progress.hidden = false;
disableInput();
Promise.resolve()
.then(_ => {
if (!navigator.bluetooth)
throw "No Web Bluetooth support.";
return navigator.bluetooth.requestDevice({
filters: anyDeviceFilter(),
optionalServices: ['generic_access']
})
})
.then(logProgress)
.then(device => {
nameInput.value = "";
return device.gatt.connect().catch(error => {
logProgress(error);
throw "Unable to connect. Some devices refuse connections.";
});
})
.then(logProgress)
.then(server => server.getPrimaryService("generic_access"))
.then(logProgress)
.then(service => service.getCharacteristic("gap.device_name"))
.then(logProgress)
.then(characteristic => {
window.deviceName = characteristic;
return characteristic.readValue();
})
.then(logProgress)
.then(value => {
window.value = value;
nameInput.value = new TextDecoder("utf-8").decode(value);
if (window.deviceName.properties.write)
enableInput();
else
throw "Name is not writable on this device.";
})
.catch(handleError)
.then(_ => progress.hidden = true)
;
cljs的Promie实现
我们怎么使用cljs实现同样的功能呢:
(refer-clojure :exclude '[resolve])(refer-clojure :exclude '[resolve])
(defn every [& args]
(js/Promise.all (into-array args)))
(defn soon
"Simulate an asynchronous result"
([v] (soon v identity))
([v f] (js/Promise. (fn [resolve]
(js/setTimeout #(resolve (f v))
500)))))
(defn resolve [v]
(js/Promise.resolve v))
;; helpers
(defn square [n] (* n n))
;; test0
(defn test0
"Synchronous version - for comparison
The code has three steps:
-get value for n
-get square of n
-get sum of n and n-squared
Note that step 3 requires access to the original value, n, and to the computed
value, n-squared."
[]
(let [n 5
n-squared (square 5)
result (+ n n-squared)]
(js/alert result)))
;; test1
(defn square-step [n]
(soon (every n (soon n square))))
(defn sum-step [[n squared-n]] ;; Note: CLJS destructuring works with JS arrays
(soon (+ n squared-n)))
(defn test1
"Array approach, flat chain: thread multiple values through promise chain by using Promise.all"
[]
(-> (resolve 5)
(.then square-step)
(.then sum-step)
(.then js/alert)))
;; test2
(defn to-map-step [array]
(zipmap [:n :n-squared] array))
(defn sum2-step [{:keys [n n-squared] :as m}]
(soon (assoc m :result (+ n n-squared))))
(defn test2
"Accumulative map approach, flat chain: add values to CLJS map in each `then` step, making
it possible for later members of the chain to access previous results"
[]
(-> (resolve 5)
(.then square-step)
(.then to-map-step)
(.then sum2-step)
;; Note: `(.then :result)` doesn't work because `:result` is not
;; recognized as a function. So we need to wrap it in an anon fn.
;; This could be easily fixed by adding a CLJS `then` function that
;; has a more inclusive notion of what a function is.
(.then #(:result %))
(.then js/alert)))
;; test3
(defn square-step-fn [n]
;; This could be called a "resolver factory" fn. It's a higher-order function
;; that returns a resolve function. `n` is captured in a closure.
(fn [n-squared]
(soon (+ n n-squared))))
(defn square-and-sum-step [n]
(-> (soon (square n))
;; note that square-step-fn is _called_ here, not referenced, in order to
;; provide its inner body with access to the previous result, `n`.
(.then (square-step-fn n))))
(defn test3
"Nested chain approach: instead of a flat list, use a hierarchy, nesting one Promise chain in another.
Uses a closure to capture the intermediate result, `n`, making it available to the nested chain."
[]
(-> (resolve 5)
(.then square-and-sum-step)
(.then js/alert)))
cljs的WebBle链
实现了
- Promise
- then
- exception
- BLE的scan和connect,其他可以继续添加
注意:使用JS函数的时候,this的bind是个坑,可以看一下参考资料中js-cljs interop的部分
(require '[goog.string :as gstring])
(refer-clojure :exclude '[resolve])
;;device.gatt.connect()
(defn conn [device]
(.
(.. device -gatt -connect)
call
(. device -gatt)))
(defn af []
(throw "good day"))
(-> (resolve (js/navigator.bluetooth.requestDevice (clj->js {"filters" anyDeviceFilter})) )
(.then conn)
(.then af)
(.catch (fn [e]
(js/console.info
(gstring/format (str e)))))
(.then js/console.info))
参考
https://blog.jeaye.com/2017/09/30/clojurescript-promesa/ https://gist.github.com/pesterhazy/74dd6dc1246f47eb2b9cd48a1eafe649 https://github.com/WebBluetoothCG/demos/blob/gh-pages/bluetooth-rename/index.html https://lwhorton.github.io/2018/10/20/clojurescript-interop-with-javascript.html