Infusing Go idioms into JavaScript via libraries

antoniovdlc

Antonio Villagra De La Cruz

Posted on July 20, 2021

Infusing Go idioms into JavaScript via libraries

Most of my professional career has revolved around JavaScript, but as a programming language enthusiast, I particularly enjoy learning about new languages. After playing a bit with Go, there were a few constructs that I felt would be useful in JavaScript as well. Here are two such constructs and some libraries I've implemented to bring them into JavaScript.


Error handling

Love it or hate it, Golang's approach to error handling is simple and straightforward.

result, err := someFunc();
if err != nil {
  // Handle error
}
// Do something with `result`
Enter fullscreen mode Exit fullscreen mode

A place within JavaScript code where this style could particularly shine, is in regards to asynchronous code, where most of the times the following code is written to handle potential errors:

try {
  const result = await someFunc()
  // Do something with `result`
} catch (err) {
  // Handle error
}
Enter fullscreen mode Exit fullscreen mode

There is nothing wrong with that particular idiom, but would it be more elegant to have a simple and less nested way to handle errors from asynchronous functions? Maybe something inspired by Go's error handling idiom like the following:

const [result, err] = await on(someFunc)
if (err != null) {
  // Handle error
}
// Do something with `result`
Enter fullscreen mode Exit fullscreen mode

To achieve that construct, you can look into the following package I've recently publish: @antoniovdlc/await-on, or any of the similar packages. At its core, the implementation of the library really revolves around these few lines of code:

async function on(fn) {
  try {
    const result = await fn();
    return [result, null];
  } catch (error) {
    return [null, error];
  }
}

export default on;
Enter fullscreen mode Exit fullscreen mode

You can have a closer look at the complete implementation at:

GitHub logo AntonioVdlC / await-on

✋🏽 - Await on asynchronous operations

await-on

version issues downloads license

Go-like error handling for async JavaScript functions.

Installation

This package is distributed via npm:

npm install @antoniovdlc/await-on

Motivation

Async functions in JavaScript are great! They allow you to write asynchronous code as if it were synchronous.

The main drawback I personally experience is having to write code like this:

try {
  const result = await someFunc()
  // Do something with `result`
} catch (err) {
  // Handle error
}
Enter fullscreen mode Exit fullscreen mode

Having had some past experience using Go, and after some time to fully understand the elegance of its simplistic error handling approach, it felt right to try to replicate it:

result, err := someFunc();
if err != nil {
  // Handle error
}
// Do something with `result`
Enter fullscreen mode Exit fullscreen mode

This is why this package exists, so that we can write asynchronous JavaScript code in a style as close as possible to that of Go:

const
Enter fullscreen mode Exit fullscreen mode

Learn more about error handling in Go: https://tour.golang.org/methods/19.


Defer statements

Another fairly neat Go feature is defer statements, which allow for some functions to only be called right before their caller function returns.

package main

import "fmt"

func main() {
  defer fmt.Println("world")

  fmt.Println("hello")
}

// Prints:
// hello
// world
Enter fullscreen mode Exit fullscreen mode

This construct is useful for releasing resources after being processed. This might be for example a connection to database, or reading from a file, or any clean-up operation we'd like to perform. By using defer statements it is easier to co-locate the allocation and de-allocation of resources.

For example, instead of writing code similar to:

const { client } = require("./db");

function getData() {
  client.connect();

  // Do things with `client` ...

  // /!\ Don't forget to close the connection /!\
  client.close();
}
Enter fullscreen mode Exit fullscreen mode

We could technically co-locate the calls to client.connect() and client.close() as follow:

const { client } = require("./db");

function getData() {
  client.connect();
  defer(() => client.close());

  // Do things with `client` ...
  // We are now sure the call to `client.close()` will be called once the body of the function has done executing.
}
Enter fullscreen mode Exit fullscreen mode

The implementation here was a little bit more tricky that for the error handling construct. As such, there is a stark difference between @antoniovdlc/defer and Go's defer statements is the order of execution of the statements (Go goes for a last-in-first-out approach, while the package linked goes for a first-in-first-out approach).

This allows us to use the following trick for synchronous functions:

function defer(fn) {
  setTimeout(fn, 0);
}
Enter fullscreen mode Exit fullscreen mode

But the above code isn't really that interesting per se.

The real trick comes with asynchronous functions! Here a wrapper function, and an Array were needed to be able to track and call all the defer statements. The defer function also needs to be passed a second argument, the caller function, due to the deprecation of Function.caller.

function deferrable(fn) {
  const f = async () => {
    const result = await fn();

    for (let i = 0, length = fn.__$_deferArr.length; i < length; i++) {
      await fn.__$_deferArr[i]();
    }

    return result;
  };

  return f;
}

function defer(fn, caller) {
  if (!Array.isArray(caller.__$_deferArr)) {
    caller.__$_deferArr = [];
  }
  caller.__$_deferArr.push(fn);
}
Enter fullscreen mode Exit fullscreen mode

Which would then yield the following construction:

const { client } = require("./db");

const getData = deferrable(async function fn() {
  await client.connect();
  defer(() => client.close(), fn);

  // Do things with `client` ...
}
Enter fullscreen mode Exit fullscreen mode

You can have a closer look at the complete implementation at:

GitHub logo AntonioVdlC / defer

⏭ - Defer statements in functions

defer

version issues downloads license

Go-like defer functions in JavaScript.

Installation

This package is distributed via npm:

npm install @antoniovdlc/defer

Motivation

Go provides the very interesting concept of defering functions until the end of a function's execution.

package main

import "fmt"

func main() {
  defer fmt.Println("world")

  fmt.Println("hello")
}

// hello 
// world
Enter fullscreen mode Exit fullscreen mode

Such built-in construct might be very useful in JavaScript for example, where we sometimes need to do some clean-up, and thus could potentially co-locate it with the instanciation.

Usage

You can use this library either as an ES module or a CommonJS package:

import { defer, deferrable } from "@antoniovdlc/defer";
Enter fullscreen mode Exit fullscreen mode

- or -

const { defer, deferrable } = require("@antoniovdlc/defer");
Enter fullscreen mode Exit fullscreen mode

defer(fn: Function, caller: Function) : void

defer takes a function as argument, which will be called at the end of the execution of…

Learn more about defer statements: https://tour.golang.org/flowcontrol/12.


Looking from inspiration at Go, and implementing some of its idioms in JavaScript was an interesting exercise, and will maybe hopefully be helpful to someone.

Which idioms and constructs in other languages would you like to see in JavaScript?

💖 💪 🙅 🚩
antoniovdlc
Antonio Villagra De La Cruz

Posted on July 20, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related