ClojureScript

Promise 互操作

直接使用 JavaScript Promise

Promise 是在 JavaScript 中處理非同步操作的常見方式。您可以在 ClojureScript 中透過呼叫 promise 方法輕鬆地使用它們。

JavaScript

Promise.resolve(42)
  .then(val => console.log(val));

ClojureScript

(.then (js/Promise.resolve 42)
       #(js/console.log %))

然而,在 ClojureScript 中鏈式 promise 方法會導致程式碼階梯式地堆疊。使用 thread-first 巨集,我們可以恢復更優雅的程式碼。

JavaScript

Promise.resolve(42)
  .then(val => console.log(val))
  .catch(err => console.log(err))
  .finally(() => console.log('cleanup'));

ClojureScript

(.finally
  (.catch
  (.then (js/Promise.resolve 42)
          #(js/console.log %))
  #(js/console.log %))
  #(js/console.log "cleanup"))

; same as above
(-> (js/Promise.resolve 42)
    (.then #(js/console.log %))
    (.catch #(js/console.log %))
    (.finally #(js/console.log "cleanup")))

大量使用 await 的 Promise 程式碼會導致更複雜且不友善的程式碼結構。參考 Puppeteer 用法中的這個範例。

JavaScript

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  try {
    await page.goto('https://example.com');
    await page.screenshot({path: 'example.png'});
  } catch (err) {
    console.log(err);
  }

  await browser.close();
})();

ClojureScript

(def puppeteer (js/require "puppeteer"))

(-> (.launch puppeteer)
    (.then (fn [browser]
             (-> (.newPage browser)
                 (.then (fn [page]
                          (-> (.goto page "https://clojure.dev.org.tw")
                              (.then #(.screenshot page #js{:path "screenshot.png"}))
                              (.catch #(js/console.log %))
                              (.then #(.close browser)))))))))

為了馴服這類型的程式碼,我們轉向使用 core.async

搭配 core.async 使用 Promise

ClojureScript 在 core.async 中提供了出色的非同步程式設計工具。其中一個特別方便的工具是 <p! 巨集,它會在 go 區塊內使用 promise。

使用 go 區塊可以讓我們編寫看起來是同步的程式碼,即使它實際上是非同步的,這就像 JavaScript 中的 awaitasync 所做的那樣。

ClojureScript

(:require
   [cljs.core.async :refer [go]]
   [cljs.core.async.interop :refer-macros [<p!]])

(def puppeteer (js/require "puppeteer"))

(go
  (let [browser (<p! (.launch puppeteer))
        page (<p! (.newPage browser))]
    (try
      (<p! (.goto page "https://clojure.dev.org.tw"))
      (<p! (.screenshot page #js{:path "screenshot.png"}))
      (catch js/Error err (js/console.log (ex-cause err))))
    (.close browser)))

這只是冰山一角。core.async 為您提供了非常強大的、類似佇列的通道,其功能遠遠不止處理一次性的 promise。

您可以在 儲存庫基本原理程式碼導覽部落格文章中閱讀更多關於 core-async 的資訊。

原始作者:Filipe Silva