Copiar bytes de Golang a JavaScript con WebAssembly

Gracias a WebAssembly podemos ejecutar código de otros lenguajes de programación desde el navegador web con JavaScript. La ventaja de WASM es que, aparte de ser rápido, permite programar en otro lenguaje y aprovechar las librerías presentes en el mismo.

Personalmente he usado Golang con WebAssembly para crear un generador de credenciales, códigos QR y códigos de barras. Todo el procesamiento y generación de imágenes se hace con Go, para luego exportar los resultados a JavaScript.

En este post voy a documentar cómo exportar un arreglo de tipo byte ([]byte) de Go a un Uint8Array de JavaScript, ya que, como te lo dije anteriormente, esto sirve cuando creamos un archivo binario con Go y queremos exportarlo a JavaScript.

Además, un Uint8Array sí puede ser transportado a través de un WebWorker usando el structured clone algorithm.

Exponer función

Yo expongo la función así:

js.Global().Set("generarCodigos", js.FuncOf(GenerarCodigos))

Aquí Global() va a devolver el ámbito global desde donde se invoca a la función de WASM.

Si estamos en un WebWorker será self, si estamos en el DOM será window y puede devolver otra cosa según el entorno.

Invocar función que devuelve Uint8Array []byte

Fíjate en que la estoy exponiendo como generarCodigos así que podré invocarla desde JavaScript con self.generarCodigos(), y que realmente la función de Go se llama GenerarCodigos cuyo código dejo a continuación:

func GenerarCodigos(this js.Value, parametros []js.Value) interface{} {
	bytesQueConformanElPDF, err := crearPdfConCodigosQr()
	if err != nil {
		return false
	}
	destinoBytesPDFEnJS := js.Global().Get("Uint8Array").New(len(bytesQueConformanElPDF))
	arreglo := parametros[0]
	longitudArreglo := arreglo.Get("length").Int()
	log.Printf("El arreglo mide %#v", longitudArreglo)
	for indice := 0; indice < longitudArreglo; indice++ {
		objeto := arreglo.Index(indice)
		nombre := objeto.Get("nombre").String()
		existencia := objeto.Get("existencia").Float()
		log.Printf("Nombre %#v existencia %#v", nombre, existencia)
	}
	js.CopyBytesToJS(destinoBytesPDFEnJS, bytesQueConformanElPDF)
	return destinoBytesPDFEnJS
}

Realmente el cuerpo de la función no es relevante. Solo estoy probando si estoy recibiendo correctamente el arreglo de objetos desde JavaScript. La parte importante viene a continuación:

Tengo el siguiente arreglo de bytes que representan un PDF:

bytesQueConformanElPDF, err := crearPdfConCodigosQr()
if err != nil {
	return false
}

Lo importante es que bytesQueConformanElPDF es un []byte.

Copiar arreglo de []byte con js.CopyBytesToJS

Ahora vamos a copiar el arreglo de bytes a JavaScript para devolverlo como resultado de la invocación. Hacemos un nuevo Uint8Array cuya longitud será la misma que la longitud del arreglo que tenemos en Go y que podemos obtener con len:

destinoBytesPDFEnJS := js.Global().Get("Uint8Array").New(len(bytesQueConformanElPDF))

Ese fragmento de código es el equivalente a new Uint8Array() de JavaScript. Luego invocamos a js.CopyBytesToJS indicando el destino y el origen. El destino será el Uint8Array que definimos desde Go usando New, y el origen será el []byte que tenemos en Go, así:

js.CopyBytesToJS(destinoBytesPDFEnJS, bytesQueConformanElPDF)

Y ahora vamos a tener ese Uint8Array como resultado de la invocación de la función. En mi caso lo tengo con Comlink y un worker expuestos a través de una Store de Vue, así que lo uso así:

const generar = async () => {
    const resultado = await dbStore.testing([
        { nombre: "Prueba", existencia: 3 },
        { nombre: "Otra cosa", existencia: 3 },
        { nombre: "Tercer elemento", existencia: 3 },
    ]);
    const blob = new Blob([resultado], { type: "application/pdf" });
    const enlace = document.createElement('a');
    const url = URL.createObjectURL(blob);
    enlace.href = url;
    enlace.download = "desde_js.pdf";
    enlace.click();
    URL.revokeObjectURL(url);
    console.log("El resultado es %o", resultado);
}

Aquí, resultado es el Uint8Array que devolvió la invocación a WebAssembly. En el ejemplo lo estoy descargando pero podemos hacer más cosas.

Conclusión

Así de simple podemos invocar a una función de Go desde JavaScript pasando y recibiendo parámetros complejos, ya que invocamos a la función con un arreglo de objetos que podemos leer en Go, y luego Go nos devuelve un Uint8Array.

Nota importante: aunque aquí uso “Go” y “JavaScript” como si se ejecutaran en entornos separados la verdad es que ambos se van a ejecutar en el navegador web gracias a WebAssembly.

Estoy aquí para ayudarte 🤝💻


Estoy aquí para ayudarte en todo lo que necesites. Si requieres alguna modificación en lo presentado en este post, deseas asistencia con tu tarea, proyecto o precisas desarrollar un software a medida, no dudes en contactarme. Estoy comprometido a brindarte el apoyo necesario para que logres tus objetivos. Mi correo es parzibyte(arroba)gmail.com, estoy como@parzibyte en Telegram o en mi página de contacto

No te pierdas ninguno de mis posts 🚀🔔

Suscríbete a mi canal de Telegram para recibir una notificación cuando escriba un nuevo tutorial de programación.

Dejar un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *