Async JS with Promises from Go

Muthukumaraswamy
4 min readOct 31, 2020

--

In JavaScript, Promise’s are the foundation of async/await.

Lets take up an example, consider the below code, This will create a Promise that resolves with a message after 3 seconds:

const p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("A happy hippo hopped and hiccupped")
}, 3000)
})

async function, the await on the Promise above, so after 3 seconds you receive the message:

// This is an async function, which can contain "await" statements inside
async function MyFunc() {
// Create the Promise
const p = new Promise((resolve, reject) => {
// After a 3 second timeout, this calls "resolve" with the message we're passing
setTimeout(() => {
resolve("A happy hippo hopped and hiccupped")
}, 3000)
})
// Await for the Promise - this resolves after 3 seconds
const message = await p
console.log(message)
}

As per the documentation, we cannot make blocking calls in Go inside a function that is invoked by JavaScript directly — if we do that, we will get into a deadlock and the app will crash. Here is what the documentation recommends, all blocking calls be inside a goroutine, which raises the problem of then returning the value to the JavaScript code.

Quote from Documentation.

Invoking the wrapped Go function from JavaScript will pause the event loop and spawn a new goroutine. Other wrapped functions which are triggered during a call from Go to JavaScript get executed on the same goroutine.
As a consequence, if one wrapped function blocks, JavaScript’s event loop is blocked until that function returns. Hence, calling any async JavaScript API, which requires the event loop, like fetch (http.Client), will cause an immediate deadlock. Therefore a blocking function should explicitly start a new goroutine.

Using a Promise is probably the best way to solve this problem: avoiding deadlocks whereas permitting programming with idiomatical JavaScript.We saw within the previous article that we are able to produce custom JavaScript objects from Go, and this is applicable to promises as well.We just need to create the Promise object by passing a function to the constructor. in a Pure-JS code , this function has two arguments, which are functions themselves, resolve should be invoked with the final result when the promise is completed. and when reject can be called when there is a an error to make the promise fail.

Here’s an updated MyGoFunc that resolves with a message after 3 seconds:

// MyGoFunc returns a Promise that resolves after 3 seconds with a message
func MyGoFunc() js.Func {
return js.FuncOf(func(this js.Value, args []js.Value) interface{} {
// Handler for the Promise: this is a JS function
// It receives two arguments, which are JS functions themselves: resolve and reject
handler := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
resolve := args[0]
// Commented out because this Promise never fails
//reject := args[1]

// Now that we have a way to return the response to JS, spawn a goroutine
// This way, we don't block the event loop and avoid a deadlock
go func() {
// Block the goroutine for 3 seconds
time.Sleep(3 * time.Second)
// Resolve the Promise, passing anything back to JavaScript
// This is done by invoking the "resolve" function passed to the handler
resolve.Invoke("A happy hippo hopped and hiccupped")
}()

// The handler of a Promise doesn't return any value
return nil
})

// Create and return the Promise object
promiseConstructor := js.Global().Get("Promise")
return promiseConstructor.New(handler)
})

Let's invoke this from a JavaScript function

async function MyFunc() {
// Get the Promise from Go
const p = MyGoFunc()
// Show the current UNIX timestamps (in seconds)
console.log(Math.floor(Date.now() / 1000))
// Await for the Promise to resolve
const message = await p
// Show the current timestamp in seconds, then the result of the Promise
console.log(Math.floor(Date.now() / 1000), message)
}

/*
Result:
1604114580
1604114580 "A happy hippo hopped and hiccupped"
*/

If your Go code errors, you can throw exceptions to JavaScript by using the reject function instead. For example:

// MyGoFunc returns a Promise that fails with an exception about 50% of times
func MyGoFunc() js.Func {
return js.FuncOf(func(this js.Value, args []js.Value) interface{} {
// Handler for the Promise
handler := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
resolve := args[0]
reject := args[1]

// Run this code asynchronously
go func() {
// Cause a failure 50% of times
if rand.Int()%2 == 0 {
// Invoke the resolve function passing a plain JS object/dictionary
resolve.Invoke(map[string]interface{}{
"message": "it worked!",
"error": nil,
})
} else {
// Assume this were a Go error object
err := errors.New("it failed")
// Create a JS Error object and pass it to the reject function
// The constructor for Error accepts a string,
// so we need to get the error message as string from "err"
errorConstructor := js.Global().Get("Error")
errorObject := errorConstructor.New(err.Error())
reject.Invoke(errorObject)
}
}()

// The handler of a Promise doesn't return any value
return nil
})

// Create and return the Promise object
promiseConstructor := js.Global().Get("Promise")
return promiseConstructor.New(handler)
})
}

While we invoke this from JavaScript, we can see the returned object about half of the times, and we get an exception the other half. Note that we’re invoking the reject function with an actual JavaScript Error object, as best practice in JavaScript!

async function MyFunc() {
try {
console.log(await MyGoFunc())
} catch (err) {
console.error('Caught exception', err)
}
}

/*
Result is either:
{error: null, message: "it worked!"}
Or a caught exception (followed by the stack trace):
Caught exception Error: Nope, it failed
*/

--

--

No responses yet