I am using web workers in my React application with a helper class that looks like this:
export class WebWorker {
constructor(worker) {
const code = worker.toString();
const blob = new Blob(["(" + code + ")()"]);
return new Worker(URL.createObjectURL(blob));
}}
The worker looks something like:
export default () => {
self.addEventListener('message', function (e) {
switch (e.data.event) {
case 'start':
// Start back ground task
break;
case 'stop':
// Stop background task
break;
}
}, false);
Then I am able to create the worker using
let sessionWorker = new WebWorker(SessionWorker);
sessionWorker.postMessage({event: "start"})
This works fine, however I now need to use a SharedWorker and I am having trouble getting it to work. All of the resources I've found show regular web workers. There is this SO Question Using Shared Worker in React but it doesn't work for me. It actually looks identical to my regular WebWorker code, but this doesn't work because SharedWorkers require that you implement an onconnect function, and I don't understand how to do that. Non-react examples that I've found show the worker script as:
onconnect = function(e) {
var port = e.ports[0];
port.onmessage = function(e) {
var workerResult = 'Result: ' + (e.data[0] * e.data[1]);
port.postMessage(workerResult);
}
}
but if I put that in my worker js file and follow the same pattern using the WebWorker helper, the worker.toString() just returns [object Window] and it never gets executed. I don't understand how to create an 'onconnect' function that will get called. I've tried variations like this:
import React from 'react';
self.onconnect = (e) => {
var port = e.ports[0];
console.log("test log");
port.onmessage = function(e) {
console.log("Message received ", e)
port.postMessage("test");
}
}
export default self;
Ultimately nothing works for me. Clearly I do not understand exports at all in Javascript. If I just export a function called onconnect it never gets called, clearly the onconnect has to belong to some sort of class-like context (like Window or Self) but I don't understand what is needed
Thankyou,
Troy.
Related
So after making this function work I started to create a loop that would give me feedback from the backend after SSR, I wanted to use hooks so I made it a functional component and started to write but the hook (even with nothing in it) is throwing 2 errors. Invalid Hook Call and A cross origin error was thrown.
I tried changing the file name to jsx, moving the file out of the folder I had because there was a second node modules in there (I thought it was using two versions of React), I also read somewhere just to clear local storage and it was just a in development using localhost problem.
*Edit So i've found that its not even calling the fn: reactToPdfUtils.savePDFNOW(sourceElement, true, undefined, cb) its stopping here
//reactToPdf.js
import React, {useEffect} from 'react';
import { savePDF } from '#progress/kendo-react-pdf';
import { drawDOM, exportPDF } from '#progress/kendo-drawing';
var ClassInstancesStore = require('../libs/goggles/reflux/classInstances-store');
var ClassInstancesActions = require('../libs/goggles/reflux/classInstances-actions');
export const savePDFNOW = (sourceElement, willSaveToDB, pageTemplate, cb) => {
//this hook broke the program as soon as i put it in even with nothing inside
useEffect(() => {
//Functionthat gets called after sending the pdf to the backend
// function onClassInstancesStoreChange(opInfo){
// var e = cloneDeep(opInfo);
// if (e.op === 'Call::StorePassportPDFToDisk') {
// if(e.error){
// console.log(e.ret)
// setPdf({ pdfErrors: e.ret })
// } else {
// console.log(e.ret)
// setPdf({ inProgress: true })
// alert('Successfully created: ' + e.ret.fileName)
// // onSubmit()
// }
// }
// };
// let listeners = [];
// listeners.push(ClassInstancesStore.listen(onClassInstancesStoreChange));
// return function cleanup() {
// _.each(listeners, function(listener) {listener();}); //NOTE: destroy listeners
// }
}, [])
try {
//do all the my functions that make my pdf perfect
} catch (error) {
//snap something went wrong all my awesome error handling
}
};
//previewer.jsx
var React = require('react');
var _ = require('underscore');
var reactToPdfUtils = require('../../../../../components/reactToPdf.js');
handleSave = (sourceElement) => {
reactToPdfUtils.savePDFNOW(sourceElement, true, undefined, cb)
function cb(sendDataContent){
if(sendDataContent.err){
console.log(sendDataContent.message)
} else {
console.log('sucess')
}
}
};
My understanding of the code is that the function handleSave will call the external hook savePDFNOW. If this is what happens, then this will break regardless of the useEffect logic.
The reason for that is that hooks that are extracted outside of the component require their name to start with use
So to allow the hook to run you change its name to useSavePDFNOW.
That being said, I believe this is not a valid use case for useEffect, think of useEffect as componentDidMount/Update. This is relevant to component render cycle rather than event listeners. It makes more sense to do away with the useEffect and keep it a regular function.
A few more things, if you are using the latest react version you don't need to import react. Also it's recommended to use const/let instead of var as well.
I have a svelte component named [symbol].svelte in which I want to initiate a connection to a streaming service to receive server-sent events. I've not found a way to do this successfully.
Since EventSource only runs in the browser, I initialized it in the onMount function like so:
<script>
export let quote;
let sse = {};
onMount(async () => {
sse = new EventSource(`https://myurl.com?symbol=${quote.symbol}`);
sse.onmessage = (event) => {
let response = JSON.parse(event.data);
if(!response.length) return;
quote = response[0];
}
});
onDestroy(() => {
if(sse.readyState && sse.readyState === 1) {
sse.close();
}
})
</script>
<div>{quote.symbol}</div>
This works fine, except when I navigate to another route that uses the same component- since the component doesn't unmount and remount, onMount() doesn't fire and thus doesn't instantiate a new SSE request. I don't know of any way to easily force the component to remount, which would be simplest (relevant github issue here)
Another try was using a reactive statement like so:
<script>
export let quote;
let sse = {};
$: {
if(process.browser === true) { //again, this stuff won't run on the server
if(sse.readyState && sse.readyState === 1) {
sse.close();
}
sse = new EventSource(`https://myurl.com?symbol=${quote.symbol}`);
}
}
sse.onmessage = (event) => {
let response = JSON.parse(event.data);
quote = response[0];
console.log(quote);
}
</script>
<div>{quote.symbol}</div>
When changing routes, the quote variable changed, thus triggering the reactive statement to kill the existing SSE and instantiate a new one. Exceptthe onmessage handler wouldn't fire, probably because the onmessage handler gets attached before the eventsource object is created.
Last take was to try with the onmessage handler in the reactive statement like so:
<script>
export let quote;
let sse = {};
$: {
if(process.browser === true) { //again, this stuff won't run on the server
if(sse.readyState && sse.readyState === 1) {
sse.close();
}
sse = new EventSource(`https://myurl.com?symbol=${quote.symbol}`);
sse.onmessage = (event) => {
let response = JSON.parse(event.data);
quote = response[0];
console.log(quote);
}
}
}
</script>
<div>{quote.symbol}</div>
The problem here is that since quote gets reassigned as a product of the onmessage handler, the reactive statement keeps firing circularly.
At this point I'm at a loss, any input would be appreciated!
It sounds like you want to use {#key ...}, which causes its contents to be torn down and recreated when the value changes, including components:
{#key quote}
<!-- destroyed and recreated whenever `quote` changes -->
<Quote {quote}/>
{/key}
Docs here: https://svelte.dev/docs#key
Incidentally, using onDestroy is unnecessary if it's only used to clean up work that happens in onMount:
onMount(() => {
const sse = new EventSource(`https://myurl.com?symbol=${quote.symbol}`);
sse.onmessage = (event) => {
let response = JSON.parse(event.data);
if(!response.length) return;
quote = response[0];
}
};
return () => {
if(sse.readyState === 1) {
sse.close();
}
});
});
This is better because you don't have the top-level sse variable, and because the returned cleanup function only needs in the browser, you don't need to have the placeholder ssr = {} assignment or check for sse.readyState.
I have a readable store in Svelte that looks like this:
const state = {};
export const channels = readable(state, set => {
let st = state;
let socket = new WebSocket("ws://127.0.0.1:5999");
socket.onmessage = function (event) {
var datastr = event.data.split(':');
st[datastr[0]].value = datastr[1];
st[datastr[0]].timestamp = Date.now();
set(st)
};
return () => {
socket.close()
}
});
When I import it to my Svelte App works. But if I put that App.svelte as my index.svelte running on Sapper, it doesnt work at first. It says error 500 websocket is not defined. Once I reload the page in the browser start to work...
I have try to parse a function that creates the store instead:
export const getChannel = () => {
// here my store
return {...store}
}
and then creating the store inside a onMount() like this:
onMount( ()=> {
const channel = getChannel();
});
But doesnt seem to do the trick... What do I miss?
Note: If a just replace the store by a simple writable, and create the websocket onMount(), it works without any problem. I just only wanted to put all the communication inside the store as a readable...
In Sapper, code in components (or imported into components) is executed in Node during server-side rendering unless it's put inside onMount (which doesn't run on the server, because there's no 'mounting' happening) or an if (process.browser) {...} block, or something equivalent.
That includes things like references to $channels causing channels.subscribe(...) to be called during initialisation.
Since there's no WebSocket global in Node, creating that subscription will fail. The simplest workaround is probably a simple feature check:
const state = {};
export const channels = readable(state, (set) => {
if (typeof WebSocket === 'undefined') return;
let st = state;
let socket = new WebSocket("ws://127.0.0.1:5999");
socket.onmessage = function (event) {
var datastr = event.data.split(":");
st[datastr[0]].value = datastr[1];
st[datastr[0]].timestamp = Date.now();
set(st);
};
return () => {
socket.close();
};
});
I'm working on an Angular 8 (with Electron 6 and Ionic 4) project and right now we are having evaluation phase where we are deciding whether to replace polling with SSE (Server-sent events) or Web Sockets. My part of the job is to research SSE.
I created small express application which generates random numbers and it all works fine. The only thing that bugs me is correct way to reconnect on server error.
My implementation looks like this:
private createSseSource(): Observable<MessageEvent> {
return Observable.create(observer => {
this.eventSource = new EventSource(SSE_URL);
this.eventSource.onmessage = (event) => {
this.zone.run(() => observer.next(event));
};
this.eventSource.onopen = (event) => {
console.log('connection open');
};
this.eventSource.onerror = (error) => {
console.log('looks like the best thing to do is to do nothing');
// this.zone.run(() => observer.error(error));
// this.closeSseConnection();
// this.reconnectOnError();
};
});
}
I tried to implement reconnectOnError() function following this answer, but I just wasn't able to make it work. Then I ditched the reconnectOnError() function and it seems like it's a better thing to do. Do not try to close and reconnect nor propagate error to observable. Just sit and wait and when the server is running again it will reconnect automatically.
Question is, is this really the best thing to do? Important thing to mention is, that the FE application communicates with it's own server which can't be accessed by another instance of the app (built-in device).
I see that my question is getting some attention so I decided to post my solution. To answer my question: "Is this really the best thing to do, to omit reconnect function?" I don't know :). But this solution works for me and it was proven in production, that it offers way how to actually control SSE reconnect to some extent.
Here's what I did:
Rewritten createSseSource function so the return type is void
Instead of returning observable, data from SSE are fed to subjects/NgRx actions
Added public openSseChannel and private reconnectOnError functions for better control
Added private function processSseEvent to handle custom message types
Since I'm using NgRx on this project every SSE message dispatches corresponding action, but this can be replaced by ReplaySubject and exposed as observable.
// Public function, initializes connection, returns true if successful
openSseChannel(): boolean {
this.createSseEventSource();
return !!this.eventSource;
}
// Creates SSE event source, handles SSE events
protected createSseEventSource(): void {
// Close event source if current instance of SSE service has some
if (this.eventSource) {
this.closeSseConnection();
this.eventSource = null;
}
// Open new channel, create new EventSource
this.eventSource = new EventSource(this.sseChannelUrl);
// Process default event
this.eventSource.onmessage = (event: MessageEvent) => {
this.zone.run(() => this.processSseEvent(event));
};
// Add custom events
Object.keys(SSE_EVENTS).forEach(key => {
this.eventSource.addEventListener(SSE_EVENTS[key], event => {
this.zone.run(() => this.processSseEvent(event));
});
});
// Process connection opened
this.eventSource.onopen = () => {
this.reconnectFrequencySec = 1;
};
// Process error
this.eventSource.onerror = (error: any) => {
this.reconnectOnError();
};
}
// Processes custom event types
private processSseEvent(sseEvent: MessageEvent): void {
const parsed = sseEvent.data ? JSON.parse(sseEvent.data) : {};
switch (sseEvent.type) {
case SSE_EVENTS.STATUS: {
this.store.dispatch(StatusActions.setStatus({ status: parsed }));
// or
// this.someReplaySubject.next(parsed);
break;
}
// Add others if neccessary
default: {
console.error('Unknown event:', sseEvent.type);
break;
}
}
}
// Handles reconnect attempts when the connection fails for some reason.
// const SSE_RECONNECT_UPPER_LIMIT = 64;
private reconnectOnError(): void {
const self = this;
this.closeSseConnection();
clearTimeout(this.reconnectTimeout);
this.reconnectTimeout = setTimeout(() => {
self.openSseChannel();
self.reconnectFrequencySec *= 2;
if (self.reconnectFrequencySec >= SSE_RECONNECT_UPPER_LIMIT) {
self.reconnectFrequencySec = SSE_RECONNECT_UPPER_LIMIT;
}
}, this.reconnectFrequencySec * 1000);
}
Since the SSE events are fed to subject/actions it doesn't matter if the connection is lost since at least last event is preserved within subject or store. Attempts to reconnect can then happen silently and when new data are send, there are processed seamlessly.
I am trying to initialize a Breeze manager inside a 'Web Worker'.
RequireJs, knockout, q, breeze are being imported inside the worker.
After a call to:EntityQuery.from('name').using(manager).execute(),
the following error appears:
Uncaught Error: Q is undefined. Are you missing Q.js? See https://github.com/kriskowal/q.
A live preview is uploaded here http://plnkr.co/edit/meXjKa?p=preview
(plunk supports downloading for easier debug).
EDIT -- relevant code
Worker.js
importScripts('knockout.js', 'q.js', 'breeze.js', 'require.js');
define('jquery', function () { return jQuery; });
define('knockout', ko);
define('q', Q); //Just trying to assign q since breeze requests Q as q
require(function () {
var self = this;
this.q = this.Q; //Just trying to assign q since breeze requests Q as q
breeze.NamingConvention.camelCase.setAsDefault();
var manager = new breeze.EntityManager("breeze/Breeze");
var EntityQuery = breeze.EntityQuery;
// Q or q here is defined (TESTED)
var test = function (name) {
return EntityQuery.from(name)
.using(manager).execute() // <-- Here q/Q breaks (I think on execute)
};
var primeData = function () {
return test('Languages')
.then(test('Lala'))
.then(test('Lala2'))
};
primeData();
setTimeout(function () { postMessage("TestMan"); }, 500);
});
Worker will be initialized on main page as:
var myWorker = new Worker("worker.js");
Ok here it goes:
Create a new requireJs and edit the
isBrowser = !!(typeof window !== 'undefined' && typeof navigator !== 'undefined' && window.document)
to
isBrowser = false
Create a new Jquery so it uses nothing related to window and generally anything that a WebWorker cannot access. Unfortunatelly i can't remember where i got this Custom JQueryJs but i have uploaded it here "https://dl.dropboxusercontent.com/u/48132252/jqueydemo.js".
Please if you find the author or the original change link and give credit.
My workerJs file looks like:
importScripts('Scripts/test.js', 'Scripts/jqueydemo.js', 'Scripts/q.js', 'Scripts/breeze.debug.js', 'Scripts/require2.js');
define('jquery', function () { return jQuery; });
require(
{
baseUrl: "..",
},
function () {
var manager = new breeze.EntityManager("breeze/Breeze");
var EntityQuery = breeze.EntityQuery;
var primeData = function () {
return EntityQuery.from(name)
.using(manager).execute() // Get my Data
.then(function (data) {
console.log("fetced!\n" + ((new Date()).getTime()));
var exportData = manager.exportEntities(); // Export my constructed entities
console.log("created!\n" + ((new Date()).getTime()));
var lala = JSON.stringify(exportData)
postMessage(lala); // Send them as a string to the main thread
})
};
primeData();
});
Finally on my mainJs i have something like:
this.testWorker = function () {
var myWorker = new Worker("worker.js"); // Init Worker
myWorker.onmessage = function (oEvent) { // On worker job finished
toastr.success('Worker finished and returned');
var lala = JSON.parse(oEvent.data); // Reverse string to JSON
manager.importEntities(lala); // Import the pre-Constructed Entities to breezeManager
toastr.success('Import done');
myWorker.terminate();
};
};
So we have managed to use breeze on a WebWorker enviroment to fetch and create all of our entities, pass our exported entities to our main breeze manager on the main thread(import).
I have tested this with 9 tables fully related to each other and about 4MB of raw data.
PROFIT: UI stays fully responsive all the time.
No more long execution script, application not responding or out of memory errors) at least for chrome
*As it makes sense breeze import entities is way more faster than the creation a full 4MB raw data plus the association process following for these entities.
By having all the heavy work done on the back, and only use import entities on the front, breeze allows you to handle large datasets 'like a breeze'.