Leveraging Web Workers and Multi-Window Communication in a Real-Time Stock Simulator
- #react
- #webworkers
- #shared
- #multiwindow
Srinivas Gowda

Creating responsive and high-performance applications in the modern web development landscape is paramount, particularly when handling complex computations and real-time data visualization. Traditional single-page applications often falter under the weight of intensive tasks, resulting in a sluggish user experience. Web Workers and multi-window communication offer innovative solutions to these challenges, revolutionizing how web applications are built.
Overcoming Performance Bottlenecks 🚀
This article explores how to harness these technologies to create a real-time stock market simulator, focusing on four key areas:
- Setting up Web Workers for background processing
- Implementing multi-window communication
- Simulating real-time stock data using worker threads
- Synchronizing data across multiple windows
Web Workers: Offloading Intensive Computations 🧠
Web Workers enable running JavaScript in background threads, separate from the main execution thread of a web application. This separation allows heavy computations to be performed without affecting the responsiveness of the user interface.
Try It Yourself 🚀
You can explore the live stock simulator application at https://solancer.github.io/stock-sim and find the full code for this project on GitHub at https://github.com/solancer/stock-sim.

Below is an implementation of a Web Worker for the stock simulation:
// public/worker.js
let simulationInterval;
let stockSymbol = 'AAPL';
let stockParams = {};
self.addEventListener('message', (event) => {
const { data } = event;
if (data.type === 'START_SIMULATION') {
const { stockSymbol: symbol, initialPrice, expectedReturn, volatility } = data.payload;
stockSymbol = symbol || 'AAPL';
stockParams = {
initialPrice: parseFloat(initialPrice) || getInitialPrice(stockSymbol),
expectedReturn: parseFloat(expectedReturn) || 0,
volatility: parseFloat(volatility) || getStockVolatility(stockSymbol),
};
startSimulation();
} else if (data.type === 'STOP_SIMULATION') {
stopSimulation();
}
});
function startSimulation() {
simulationInterval = setInterval(() => {
// Simulation logic here
// Calculate new price
// Send updates via postMessage
}, 1000);
}
This Worker listens for messages to start or stop the simulation and sends regular updates back to the main thread.
Code Breakdown
In the `worker.js` file, the Worker is set up to handle messages from the main thread. It listens for two types of messages: 'START_SIMULATION' and 'STOP_SIMULATION'. Upon receiving a 'START_SIMULATION' message, it extracts the stock parameters and initiates the simulation by calling `startSimulation()`. The simulation runs at regular intervals (every second in this case), calculating new stock prices and sending updates back to the main thread using `postMessage`.
Multi-Window Communication: Divide and Conquer 🖥️🔀🖥️
The stock simulator runs multiple simulations in separate windows, an architecture that allows for better isolation and scalability.

The following code demonstrates how to set up communication between these windows:
// src/App.jsx
const createWorkerWindow = useCallback(() => {
const workerId = `worker-${Date.now()}`;
const stockSymbol = selectedStock;
if (runningSimulations[stockSymbol]) {
alert(`A simulation for ${stockSymbol} is already running.`);
return;
}
const stockParams = customStocks[stockSymbol] || {};
const queryParams = new URLSearchParams({
id: workerId,
stock: stockSymbol,
initialPrice: stockParams.initialPrice != null ? stockParams.initialPrice : '',
expectedReturn: stockParams.expectedReturn != null ? stockParams.expectedReturn : '',
volatility: stockParams.volatility != null ? stockParams.volatility : '',
});
const workerWindow = window.open(
`/worker.html?${queryParams.toString()}`,
'_blank',
`width=550,height=650,left=${leftPosition},top=${topPosition}`
);
if (workerWindow) {
workerWindow.name = workerId;
setRunningSimulations((prev) => ({ ...prev, [stockSymbol]: workerWindow }));
} else {
alert('Please allow pop-ups for this application.');
}
}, [selectedStock, customStocks, runningSimulations]);
This function opens a new window for each stock simulation, passing necessary parameters via URL query strings.
Code Explanation
In `App.jsx`, the `createWorkerWindow` function is responsible for opening a new window for each stock simulation. It generates a unique `workerId` and checks if a simulation for the selected stock symbol is already running. If not, it retrieves the stock parameters and constructs query parameters to pass to the new window. The `window.open` method opens a new window with the specified URL and dimensions. If the window is successfully opened, it adds the new window to the `runningSimulations` state for tracking.
Synchronizing Data: Keeping Everyone in the Loop 🔄
To synchronize data across multiple windows, the `postMessage` API is utilized for inter-window communication.

Worker Window Communication
In the worker window:
// public/worker.html
const mainWindow = window.opener || window.parent;
worker.onmessage = (event) => {
const { data } = event;
if (data.type === "STOCK_UPDATE") {
mainWindow.postMessage(
{
type: "STOCK_UPDATE",
payload: { stockSymbol, stockUpdate: data.payload },
},
mainWindow.location.origin
);
updateChart(data.payload);
}
// Handle other message types...
};
In this code snippet, the worker window obtains a reference to the main window using `window.opener` or `window.parent`. The worker listens for messages from the Web Worker (`worker.onmessage`) and forwards stock updates to the main window using `mainWindow.postMessage`.
Main Application Communication
In the main application:
// src/App.jsx
useEffect(() => {
const handleMessage = (event) => {
if (event.origin !== window.location.origin) return;
const { data } = event;
if (data.type === 'STOCK_UPDATE') {
setStockData((prevData) => ({
...prevData,
[data.payload.stockSymbol]: [
...(prevData[data.payload.stockSymbol] || []),
{ time: data.payload.time, price: data.payload.price },
],
}));
}
// Handle other message types...
};
window.addEventListener('message', handleMessage);
return () => window.removeEventListener('message', handleMessage);
}, []);
The main application sets up an event listener for messages from worker windows. When a 'STOCK_UPDATE' message is received, it updates the local state with the new stock data. This mechanism ensures that all windows have the most up-to-date information, with the main window acting as the central hub for data management.

Conclusion: A New Frontier in Web Performance 🏁
Leveraging Web Workers and multi-window communication enables the creation of powerful and responsive real-time applications like a stock market simulator. This approach allows complex simulations to run without blocking the main thread, resulting in a smooth and engaging user experience.
The techniques demonstrated can be applied to a wide range of applications beyond stock simulations. Any web application that requires intensive computations or real-time data processing can benefit from this architecture.
However, managing Web Workers and multi-window communication can still be quite challenging at scale.
Exploring neo.mjs: Simplifying Multithreaded Development
For developers looking to further simplify multithreaded development while enhancing performance, neo.mjs neo.mjs is an excellent framework to explore. neo.mjs allows you to handle multithreading more efficiently by abstracting complex processes like worker management and inter-thread communication. With its modern approach, you can easily build applications with high performance while maintaining smooth user experiences.
Key features of neo.mjs include:
Multi-threading
- Uses OMT (Off the Main Thread).
- Runs in an Application Worker for non-blocking user interaction.
- Extra workers for tasks, updates, and caching via ServiceWorker.
Multi-Window Apps
- No need for native shell (e.g., Electron).
- Share data/state and components across windows with same JS instances.
Modern JS in Browser
- Dev-mode without transpiling/compiling.
- Uses latest ECMAScript features, supports debugging, reduces costs.
Component Library
- Declarative component trees and higher-order components.
- Ready-to-use components, lazy-loaded forms, multiple nested themes.
State Management
Multiple state providers, support observables and scalability. Adapts to different architectures like MVVM.
Core Features
Cross-realm RPC, scalability, drag & drop, mixins, and plugins.
By incorporating neo.mjs into your project, you can take advantage of its advanced features to further enhance the scalability and performance of your multithreaded applications, especially for real-time scenarios like the stock simulator.
Check out the neo.mjs documentation for more information on how to get started with this powerful framework.
