Run Go-WebAssembly before onmessage event in a Web Worker - javascript

I'm trying to include a Go-WebAssembly function inside a JavaScript Web Worker, and the problem is that the event onmessage from the worker runs before the WebAssembly loads so everytime I call the WebAssembly function I got an error: "yourFunction is not defined". I hope you can help me figuring out how to solve this problem or you can give me ideas how to implement this. Thanks !
A simplified version of my code:
main.go
package main
import (
"fmt"
"log"
"syscall/js"
)
func myGoFunction(this js.Value, i []js.Value) interface{} {
//Do some hard work
fmt.Println(i[0])
return true
}
func main() {
js.Global().Set("myGoFunction", js.FuncOf(myGoFunction))
<-make(chan bool)
}
main.js
const doSomething = () => {
if (myArray.length > 0)
worker.postMessage({ value: myArray.shift() })
}
const init = () => {
if (worker) worker.terminate()
worker = new Worker('worker.js')
worker.postMessage({ a: A, b: B, bool: true })
worker.onmessage = doSomething
}
init()
worker.js
importScripts('wasm_exec.js');
const go = new Go();
WebAssembly.instantiateStreaming(fetch("main.wasm"), go.importObject).then((result) => {
go.run(result.instance);
});
onmessage = (e) => {
const {settings} = e.data
if (settings) {
//set some values
} else {
for (let i= 0; i < 1000000; i++)
someArray[i] = calculate(i)
postMessage({someArray})
}
}
const calculate = (i) => {
//Do more
//Here is where I call the go function
myGoFunction(i)
}
Something I did to see if the myGoFunction is loading, is to put the WebAssembly.instantiateStreaming into a promise then call the onmessage but of course this will load the WebAssembly.instantiateStreaming millions of times and the job is done but extremely slow. Or maybe I implemented the promises the wrong way. I don't know, please help. :D

You can store the promise returned from WebAssembly.instantiateStreaming() and await it in your onmessage handler:
const waInit = WebAssembly.instantiateStreaming(fetch("main.wasm"), go.importObject).then((result) => {
go.run(result.instance);
});
onmessage = async (e) => {
await waInit; // now WA is ready
const {settings} = e.data
// The rest of your handler

Related

How to use asynchronous javascript code with web workers in a class

I wrote a javascript class, that upon instantiation creates a web worker. Another method in this class runs the worker. I want to invoke the method running the worker and then do something afterwards asynchronously. For a better understanding I added a schematic code skeleton below.
I tried to generate and store a Promise in a class variable and then act upon it, but it seemed to be a wrong ansatz.
Link to the JS Bin.
class Task{
constructor(){
this.data = 0;
//this.listen; //my idea, does not work
this.worker = new Worker('worker.js');
this.worker.onmessage = (function(e){
// worker constantly emits messages
let data = e.data.split(' ');
// suppose message is now availabe as array
this.data = data;
if(data.includes("trigger")){
//trigger signals that the heavy computation is over
/* my idea didn't work:
*this.listen = Promise.resolve(1);
*/
}
}).bind(this);
}
async doTask(){
this.worker.postMessage("start computation");
//wait for the worker to finish calculation
/*my idea
*await this.listen;
*/
//do something after trigger was sent
return 0;
}
}
perhaps something like this?
class Task{
constructor(){
this.data = 0;
this.worker = new Worker('worker.js');
}
async calculation() {
if (this.worker.onmessage === null) {
return new Promise(resolve => {
this.worker.onmessage = e => {
let data = e.data.split(' ');
// suppose message is now availabe as array
this.data = data;
if(data.includes("trigger")){
this.worker.onmessage = null;
resolve();
}
};
this.worker.postMessage("start computation");
});
} else {
console.log('One calc at a time please...');
}
}
async doTask(){
await this.calculation();
//do something after trigger was sent
return 0;
}
}

How to create/postmessage to web workers in an array

Question is simple:
Example:
For (iterate based on amount of cores){
Let worker = workers[I]
Worker.postmessage
}
End of example .
Disclaimer: This example only shows what is expected of the end result and is in no means in what is considered "working condition" . Also note that the method used above does not return a worker for "workers[iterator]" instead just undefined.
Objective: Create working methods:
1: make array of unknown amount of workers(based on cores).
2: once that array is built, post a message to each worker and have a returned result(other than undefined).
Note: I do have a hypothesis of why it does not work:
1: web workers are created and are only accessable through the event that created them and its only acception is the onmessage "event" handler .
in defiance of my hypothesis there is such things that would say neigh to what is written above for example , like thread.js that allows for thread pooling and other procedures.
This is the main reason of why I ask , because I do know it is possible but would like a simple answer.
Thanks for your time .
Here is an example:
function createWorker (workerScript) {
const blob = new Blob([`(${workerScript})(self)`], {type: 'application/javascript'});
return new Worker(URL.createObjectURL(blob));
};
function workerCode (self) {
self.onmessage = function (message) {
postMessage(`Data from worker: ${message.data}`);
};
};
// assuming that you will send only one message to the worker,
// and that the worker will produce only one message too.
function workerPromise (worker, message) {
const promise = new Promise((resolve, reject) => {
worker.onmessage = resolve;
}).then(message => message.data);
worker.postMessage(message);
return promise;
}
(async () => {
const workers = [];
for (let i = 0; i < navigator.hardwareConcurrency; i++) {
workers.push(createWorker(workerCode));
}
const results = await Promise.all(
workers.map((w, index) => workerPromise(w, `worker ${index}`))
);
console.log(results);
})();
Start with the "Examples" section under: https://developer.mozilla.org/en-US/docs/Web/API/NavigatorConcurrentHardware/hardwareConcurrency
Modified example:
// in Main thread
const numberOfCPUCores = window.navigator.hardwareConcurrency;
const workerList = [];
const cpuWorkerMessageHandler = event => {
// process message from a worker by accessing: event.data
}
for (let i = 0; i < numberOfCPUCores; i++) {
const newWorker = new Worker('cpuworker.js');
newWorker.addEventListener("message", cpuWorkerMessageHandler);
workerList.push(newWorker);
}
// then, when done with all processing, terminate all workers
workerList.forEach(w => w.terminate());

nodejs write file frequently

I have a listener to listen for the change of content, once the content modified, it will emit the handler function:
$('#editor').on('onchange', () => changeHandler('...','...'));
function changeHandler(filePath, content){
var ws = fs.createWriteStream(filePath, 'utf8');
ws.write(content);
}
My problem is that the 'onchange' occurs too often, so 'write file' too often handles, it may lost data during the period.
Can someone give any suggestion?
Update
Now I've changed code according the answers below looks like:
this.buffer = null; //used to cache
// once content changed, maybe too often
changeHandler() {
if (this.editor.curOp && this.editor.curOp.command.name) {
var id = $('.nav-items li.active .lk-hosts').attr('data-hosts-id');
var content = this.editor.getValue();
// cache data, not immediately write to file
this.buffer = {id: id, content: content};
}
}
setInterval(()=> {
// means there's data in cache
if (this.buffer !== null) {
let id = this.buffer.id;
let content = this.buffer.content;
// reset cache to null
this.buffer = null;
// write file
this.writeContent(id, content, (err)=> {
})
}
}, 800);
Thanks all answers!
Why not simply build a buffer to collect written text then write to file only when you have a certain number of writes:
$('#editor').on('onchange', () => changeHandler('...','...'));
var writeBuffer = ''; // can also make this an array
var writeBufferSize = 0;
var filePath = 'path_to_file';
var ws = fs.createWriteStream(filePath, 'utf8');
function changeHandler(content){
if (writeBufferSize == SOME_THRESHOLD) {
ws.write(writeBuffer);
writeBuffer = '';
writeBufferSize = 0;
} else {
writeBuffer += content + '\n';
writeBufferSize++;
}
}
If you choose a write buffer threshold that's too big, you might want to delegate the write to some worker thread to be done in parallel, and in this case you can create another temporary write buffer to fill out while the original is being written, then switch the two.
This sample below shows how to make debounced event handling, although it's not node.js code it's same in concept.
// eventEmitter variable to use
var emitter = new EventEmitter();
// dom element change event
$('#editor').on('input', function(event) {
emitter.emit('changeEvent', event.target.value);
});
// event listener, which debounces change event of input
emitter.on('changeEvent', debounce(function(data) {
writeFile('li', data);
}, 1000)); // <== debounce for 1second
// sample emitter, for demo
// we don't have access to nodejs EventEmitter class in Stackoverflow
// don't use in production
function EventEmitter() {
var callbacks = [];
return {
on: function(eventName, fn) {
callbacks.push({
eventName: eventName,
callback: fn
})
},
emit: function(eventName, payload) {
var fn = callbacks.find(function(item) {
return item.eventName === eventName;
});
if (fn) {
fn.callback(payload);
}
}
}
}
// simple logger for demo purpose
// emulates write file
function writeFile(name, content) {
var $elem = $(document.createElement(name));
$elem.text(content);
$('#logger').append($elem);
}
// throttle function - reduces fn call with timeout
// credits: https://remysharp.com/2010/07/21/throttling-function-calls
function debounce(fn, delay) {
var timer = null;
return function() {
var context = this,
args = arguments;
clearTimeout(timer);
timer = setTimeout(function() {
fn.apply(context, args);
}, delay);
};
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<textarea id="editor" placeholder="Enter text, this will emit change event"></textarea>
<p>
Notice the 1sec throttle (write something, pause for 1sec, write again)
</p>
<ul id="logger"></ul>
The debounce function can be also used on textarea change event
// debounce emitting
$('#editor').on('input', debounce(function(event) {
emitter.emit('changeEvent', event.target.value);
}, 1000));
// write file when received event without debounce
emitter.on('changeEvent', function(data){
logElement('li', data);
});
The Underscore library has _.throttle() and _.debounce() functions.

What's the best(right) way to write a polling method (with Typescript & AngularJS)?

I am trying to write a polling method that polls a server periodically to check whether a zip file has already been created or not.
What I want to accomplish are the following:
Calls(ajax) an API that creates a zip file on server
Calls(ajax) another API that checks if the zip file has already been created (polling method)
Some subsequent process
Here is my code snippet ↓
var success: boolean = false;
//1. requests a server to create a zip file
this.apiRequest.downloadRequest(params,ApiUrl.URL_FOR_DOWNLOAD_REQUEST)
.then((resObj) => {
var apiRes: IDownloadService = resObj.data;
if (apiRes.status[0].statusCode == "000") {
success = true;
} else {
//Error
}
}).then(() => {
if (success) {
//2. polls the server to check if the zip file is ready
<- Polling method↓ ->
this.polling(params).then((zipUrl) => {
console.log(zipUrl); //always logs zipUrl
//some subsequent process...
});
}
});
Could anyone give some examples of polling method that would work in this case?
Added:
private polling(params: any): ng.IPromise<any> {
var poller = () => this.apiRequest.polling(params, ApiUrl.URL_FOR_POLLING);
var continuation = () => poller().then((resObj) => {
var apiRes: IDownloadService = resObj.data;
if (apiRes.zipFilePath == "") {
return this.$timeout(continuation, 1000);
} else {
return apiRes.zipFilePath;
}
})
var result: ng.IPromise<any> = continuation();
return result;
}
Basically abstract the methods out as shown below:
let poll = () => this.apiRequest.downloadRequest(params,ApiUrl.URL_FOR_DOWNLOAD_REQUEST)
let continuation = () => poll().then((/*something*/)=> {
/*if still bad*/ return continuation();
/*else */ return good;
})
continuation().then((/*definitely good*/));
Update
As requested in the comment below:
return this.$timeout(continuation, 1000);
This is needed to get angular to kick off a digest cycle.

RxJS: Producer-consumer with abort

I've got a special producer consumer problem in RxJS: The producer slowly produces elements. A consumer is requesting elements and often has to wait for the producer. This can be achieved by zipping the producer and the request stream:
var produce = getProduceStream();
var request = getRequestStream();
var consume = Rx.Observable.zipArray(produce, request).pluck(0);
Sometimes a request gets aborted. A produced element should only consumed after a not aborted request:
produce: -------------p1-------------------------p2--------->
request: --r1--------------r2---------------r3-------------->
abort: ------a(r1)------------------a(?)------------------>
consume: ------------------c(p1, r2)-------------c(p2, r3)-->
The first request r1 would consume the first produced element p1, but r1 gets aborted by a(r1) before it can consume p1. p1 is produced and gets consumed c(p1, r2) on second request r2. The second abort a(?) is ignored, because no unanswered request happened before. The third request r3 has to wait on the next produced element p2 and is not aborted till p2 is produced. Thus, p2 is consumed c(p2, r3) immediately after it got produced.
How can I achieve this in RxJS?
Edit:
I created an example with a QUnit test on jsbin. You can edit the function createConsume(produce, request, abort) to try/test your solution.
The example contains the function definition of the previously accepted answer.
This (core idea minus details) passes your JSBin test:
var consume = request
.zip(abort.merge(produce), (r,x) => [r,x])
.filter(([r,x]) => isNotAbort(x))
.map(([r,p]) => p);
And the JSBin code.
I can't quite wrap my brain around how to do it with existing operators. Here's how to do it with Observable.create():
return Rx.Observable.create(function (observer) {
var rsub = new Rx.SingleAssignmentDisposable();
var asub = new Rx.SingleAssignmentDisposable();
var psub = new Rx.SingleAssignmentDisposable();
var sub = new Rx.CompositeDisposable(rsub, asub, psub);
var rq = [];
var pq = [];
var completeCount = 0;
var complete = function () {
if (++completeCount === 2) {
observer.onCompleted();
}
};
var consume = function () {
if (pq.length && rq.length) {
var p = pq.shift();
var r = rq.shift();
observer.onNext('p' + p);
}
};
rsub.setDisposable(request.subscribe(
function (r) {
rq.push(r);
consume();
},
function (e) { observer.onError(e); },
complete));
asub.setDisposable(abort.subscribe(
function (a) {
rq.shift();
},
function (e) { observer.onError(e); }
));
psub.setDisposable(produce.subscribe(
function (p) {
pq.push(p);
consume();
},
function (e) { observer.onError(e); },
complete));
return sub;
});
http://jsbin.com/zurepesijo/1/
This solution ignores aborts that don't follow an unanswered request:
const {merge} = Rx.Observable;
Rx.Observable.prototype.wrapValue = function(wrapper) {
wrapper = (wrapper || {});
return this.map(function (value) {
wrapper.value = value;
return wrapper;
});
};
function createConsume(produce, request, abort) {
return merge(
produce.wrapValue({type: 'produce'}),
request.wrapValue({type: 'request'}),
abort.wrapValue({type: 'abort'})
)
.scan(
[false, []],
([isRequest, products], e) => {
// if last time the request was answered
if (isRequest && products.length) {
// remove consumed product
products.shift();
// mark request as answered
isRequest = false;
}
if (e.type === 'produce') {
// save product to consume later
products.push(e.value);
} else {
// if evaluated to false, e.type === 'abort'
isRequest = (e.type === 'request');
}
return [isRequest, products];
}
)
.filter( ([isRequest, products]) => (isRequest && products.length) )
.map( ([isRequest, products]) => products[0] ); // consume
}
Code in newest test on JSBin.

Categories