I like Node.js most of the time. A lot of my affinity can be attributed to the fact that I tend to reuse stuff I’ve written. I can get things done pretty quickly because, at some point, I’ve already put in the work.

Now go is still a mystery to me. I see the appeal and I’ve used some phenomenal programs written in go. And a disturbing number of those seems to have come from Matt Holt, but maybe we’ll talk about that some other day.

One thing I love about go is the function return style where you assign the error and the result together and check if there was an error. It’s reminds me of js callbacks only without the callback hell.

result, err := Sqrt(-1)
if err != nil {
    log.Fatal(err)
}
fmt.Println(result)
mySqRtFunc(-1, (err, result) => {
  if(err) return console.error(err)

  console.log(result)
})

Earlier this week I was updating some dependencies and got stuck on what I consider a simple package, https://www.npmjs.com/package/nanoid.

Nano ID npmjs.com

I banged my head on my desk for several minutes trying to update my old javascript code from this…

const { nanoid } = require('nanoid')

into this…

import { nanoid } from 'nanoid'
Uncaught SyntaxError: Cannot use import statement outside a module

To be honest it feels like I took a few years sabbatical and the JavaScript world passed me by. I’ve used import in typescript with angular but not in .js. Also a lot of npm docs now just throw the await keyword at the top level.

I mean look at this guy!

pocketbase.io

Now this is another one of those impressive go projects. This one runs the backend using go and there is a JavaScript SDK available for the frontend. But look at that! import and await just hanging out on the example page. Clearly I’m missing something!

As a result I decided to switch gears and learn about js modules vs. common javascript. As you may have guessed most of my experience is with common js. All my files end in .js. And I have avoided using the .mjs or .cjs extensions. Call me an old curmudgeon.

I used to write all my functions with callbacks.

const myCallbackFunc = (stuff, next) => {
  let result
  try {
    result = JSON.parse(stuff)
  } catch (err) {
    return next(err)
  }
  next(null, result)
}

myCallbackFunc('things', (err, result) => {
  if(err) return console.error(err)

  console.log(result)
})

This works fine until you end up taking the result of functions and passing them on to others.

doThing1(input1, (err, result1) => {
  if(err) return console.error(err)

  doThing2(result1, (err, result2) => {
    if(err) return console.error(err)

    doThing3(result2, (err, result3) => {
      if(err) return console.error(err)

      // now what?
    })
  })
})

We can simplify by taking this mess and moving it into a promise. Then when we need result3 for the Nth time, we can get it without the giant pyramid.

const myPromise = input1 => {
  return new Promise((resolve, reject) => {
    doThing1(input1, (err, result1) => {
      if(err) return reject(err)

      doThing2(result1, (err, result2) => {
        if(err) return reject(err)

        doThing3(result2, (err, result3) => {
          if(err) return reject(err)

          resolve(result3)
        })
      })
    })
  })
}

myPromise(myInput).then(result3 => {
  console.log(result3)
}).catch(err => {
  console.error(err)
})

We can clean this up further (or maybe not) by wrapping this in an async IFFE but we still have to catch the error.

(async () => {
  let result
  try {
    result = await myPromise(myInput)
  } catch (err) {
    return console.error(err)
  }
  console.log(result)
})()

One cool thing I learned about using the .mjs extension or package.json {"type": "module"} or html <script type="module">, is that you can call await at the top level! See more cool stuff here. No need for the IFFE!

let result
try {
  result = await myPromise(myInput)
} catch (err) {
  return console.error(err)
}
console.log(result)

Cool! Now things are starting to look real clean but we still have that try catch…

Another cool thing I learned is that JS has destructuring assignment. We can use this to assign an error as well as a result in an array.

const myGoLikeFunction = async input1 = {
  try {
    return [null, await myPromise(input1)]
  } catch (err) {
    return [err, null]
  }
}

const [err, result] = await myGoLikeFunction(myInput)

Now this looks a lot more like that hip new kid on the block, go.

It’s a little unorthodox but at first glance I get a strong sense of what’s happening. I love this pattern and you can bet I’ll be using it.