Piloting Puppeteer with PureScript - Part 2
Tags: webdev, javascript, functional
Published: 2021-02-04 07:21:47

---
title: Piloting Puppeteer with PureScript - Part 2
published: true
description: How to do JS interop with PureScript
tags:
 - webdev
 - javascript
 - functional
cover_image: https://images.unsplash.com/photo-1599557859276-2b1363e56df6?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=1189&q=80
---

**tl;dr** [Here's the GitHub repo.](https://github.com/meeshkan/purescript-puppeteer-tutorial)

In the previous article in this series, I touted the benefits of using PureScript `Aff`-s to orchestrate complicated asynchronous workloads like running Puppeteer on AWS serverless.

In this article, I'd like to explore how that code links to the JS layer. PureScript has a straightforward FFI that allows developers to write functions in vanilla JS that are interpreted as strongly-typed functions in the PureScript layer. While nothing prevents an error in typing the JS functions, there is a way to deal with exceptions - using the `Effect` monad.  Let's see how.

# Launching a browser

Let's look at the PureScript code for the functions that get the chromium binary and launches the browser. The code is located in [`Main.purs`](https://github.com/meeshkan/purescript-puppeteer-tutorial/blob/main/src/Main.purs).

```haskell
type EPromise a
  = Effect (Promise a)

foreign import executablePath_ :: EPromise String

executablePath = asAff executablePath_ :: Aff String

type LaunchBrowser_
  = String -> EPromise Browser

foreign import launchBrowser_ :: LaunchBrowser_

launchBrowser = asAff launchBrowser_ :: Affize LaunchBrowser_
```

There are several things going on in those functions, some of which will be the subject of the next articles. In this article, we'll focus just on the foreign declarations and show how they are implemented in the JS layer.

# Show me the JS!

The functions that are used to make this possible are in [`Main.js`](https://github.com/meeshkan/purescript-puppeteer-tutorial/blob/main/src/Main.js).

```javascript
var chromium = require("chrome-aws-lambda");

exports.executablePath_ = function() {
  return chromium.executablePath;
};

exports.launchBrowser_ = function(executablePath) {
  return function() {
    return chromium.puppeteer.launch({
      args: chromium.args,
      defaultViewport: chromium.defaultViewport,
      executablePath: executablePath,
      headless: chromium.headless,
      ignoreHTTPSErrors: true,
    });
  };
};
```

Specifically, let's compare the two `executablePath_`-s. The first thing to note is that PureScript returns a _function_ even though executablePath_ is a _value_ of type `Effect (Promise String)` (shortened to `EPromise` via the helper type defined above). In PureScript, `Effect a` needs to be implemented as `function () { return a }` in JavaScript. This is because the `Effect` monad _always_ has the possibility of a side effect or error being produced (because JavaScript is unsafe and full of side effects, like writing to the hard drive) and thus wraps it in a function to avoid immediate execution.  This allows us to reason about unsafe (impure) code in a safe (pure) way, just like the `IO` monad in Haskell.

So, there are only two ways we can really mess this up:

- Returning the wrong type from the JS.
- Unintentionally provoking an error in the JS.

In PureScript programs, these are the most common sources of bugs, but they are easily spotable because the interface is really clean: it will always be in some module transpiled to a file called `foreign.js`.  However, because the surface area of these JS-interop functions is quite small (usually on the order of a couple lines, like the functions above), the chances there will be a bug are much smaller than a whole program written in JS or TS.

# Promises, promises...

In the case of the functions above, they return effectful `Promise`-s. PureScript has no idea what a Promise is (remember from the last article that we're using `Aff`-s, so it treats the promise like an opaque blog and happily passes it to the next consumer, which marshals it into an `Aff`.

In the [next article](https://dev.to/mikesol/piloting-puppeteer-with-purescript-part-3-41df), I'll talk about how the type system can enforce the use of a consistent asynchronous context, namely `Aff`, so that we don't have to juggle promises, callbacks, async/await and vanilla code.