Last active
October 7, 2020 03:37
-
-
Save ItalyPaleAle/cb5b88bb0e87181fec43bd945539fc47 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Copyright (C) 2020 Alessandro Segala (ItalyPaleAle) | |
// License: MIT | |
// MyGoFunc fetches an external resource by making a HTTP request from Go | |
// The JavaScript method accepts one argument, which is the URL to request | |
func MyGoFunc() js.Func { | |
return js.FuncOf(func(this js.Value, args []js.Value) interface{} { | |
// Get the URL as argument | |
// args[0] is a js.Value, so we need to get a string out of it | |
requestUrl := args[0].String() | |
// Handler for the Promise | |
// We need to return a Promise because HTTP requests are blocking in Go | |
handler := js.FuncOf(func(this js.Value, args []js.Value) interface{} { | |
resolve := args[0] | |
reject := args[1] | |
// Run this code asynchronously | |
go func() { | |
// Make the HTTP request | |
res, err := http.DefaultClient.Get(requestUrl) | |
if err != nil { | |
// Handle errors: reject the Promise if we have an error | |
errorConstructor := js.Global().Get("Error") | |
errorObject := errorConstructor.New(err.Error()) | |
reject.Invoke(errorObject) | |
return | |
} | |
// We're not calling res.Body.Close() here, because we are reading it asynchronously | |
// Create the "underlyingSource" object for the ReadableStream constructor | |
// See: https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream/ReadableStream | |
underlyingSource := map[string]interface{}{ | |
// start method | |
"start": js.FuncOf(func(this js.Value, args []js.Value) interface{} { | |
// The first and only arg is the controller object | |
controller := args[0] | |
// Process the stream in yet another background goroutine, | |
// because we can't block on a goroutine invoked by JS in Wasm | |
// that is dealing with HTTP requests | |
go func() { | |
// Close the response body at the end of this method | |
defer res.Body.Close() | |
// Read the entire stream and pass it to JavaScript | |
for { | |
// Read up to 16KB at a time | |
buf := make([]byte, 16384) | |
n, err := res.Body.Read(buf) | |
if err != nil && err != io.EOF { | |
// Tell the controller we have an error | |
// We're ignoring "EOF" however, which means the stream was done | |
errorConstructor := js.Global().Get("Error") | |
errorObject := errorConstructor.New(err.Error()) | |
controller.Call("error", errorObject) | |
return | |
} | |
if n > 0 { | |
// If we read anything, send it to JavaScript using the "enqueue" method on the controller | |
// We need to convert it to a Uint8Array first | |
arrayConstructor := js.Global().Get("Uint8Array") | |
dataJS := arrayConstructor.New(n) | |
js.CopyBytesToJS(dataJS, buf[0:n]) | |
controller.Call("enqueue", dataJS) | |
} | |
if err == io.EOF { | |
// Stream is done, so call the "close" method on the controller | |
controller.Call("close") | |
return | |
} | |
} | |
}() | |
return nil | |
}), | |
// cancel method | |
"cancel": js.FuncOf(func(this js.Value, args []js.Value) interface{} { | |
// If the request is canceled, just close the body | |
res.Body.Close() | |
return nil | |
}), | |
} | |
// Create a ReadableStream object from the underlyingSource object | |
readableStreamConstructor := js.Global().Get("ReadableStream") | |
readableStream := readableStreamConstructor.New(underlyingSource) | |
// Create the init argument for the Response constructor | |
// This allows us to pass a custom status code (and optionally headers and more) | |
// See: https://developer.mozilla.org/en-US/docs/Web/API/Response/Response | |
responseInitObj := map[string]interface{}{ | |
"status": http.StatusOK, | |
"statusText": http.StatusText(http.StatusOK), | |
} | |
// Create a Response object with the stream inside | |
responseConstructor := js.Global().Get("Response") | |
response := responseConstructor.New(readableStream, responseInitObj) | |
// Resolve the Promise | |
resolve.Invoke(response) | |
}() | |
// The handler of a Promise doesn't return any value | |
return nil | |
}) | |
// Create and return the Promise object | |
// The Promise will resolve with a Response object | |
promiseConstructor := js.Global().Get("Promise") | |
return promiseConstructor.New(handler) | |
}) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment