JavaScript 函数阻止 Web 套接字并导致同步问题和延迟



我有一个web套接字,它每100到200ms从web套接字服务器接收一次数据(我已经尝试过使用共享web worker以及main.js文件中的所有内容),

当新的JSON数据到达时,我的main.js会运行filter_JSON_run_all(JSON_data),它会更新Tabulator.js&Dygraph.js表格;基于值是增加还是减少的带有一些自定义颜色编码的图形

1)web套接字json数据(每100ms或更短)->2) 运行函数filter_json_run_all(json_data)(花费150到200ms)->3) 重复1&2永远

传入json数据的时间戳很快就会相对于实际时间延迟(json_time 15:30:12 vs实际时间:15:31:30),因为filter_json_run_all导致操作积压。

因此,它会导致不同电脑上的用户根据打开或刷新网站的时间出现网络套接字同步问题。

这只是由长filter_json_run_all()函数引起的,否则,如果我所做的只是console.log(json_data),它们将完全同步。

如果有人知道我如何防止由于javascript运行缓慢而导致的这种对传入JSON websocket数据的阻塞/积压,我将不胜感激功能:)

我试着使用一个共享的web worker,它可以工作,但它不能绕过被filter_json_run_all()阻止的main.js中的延迟,我不认为我可以放filter_json_lun_all),因为所有的图&表对象是在main&当我点击表格手动更新值时,我也有回调(双向网络套接字)

如果你有任何想法或建议,我将非常感谢:)

worker.js:

const connectedPorts = [];
// Create socket instance.
var socket = new WebSocket(
'ws://'
+ 'ip:port'
+ '/ws/'
);

// Send initial package on open.
socket.addEventListener('open', () => {
const package = JSON.stringify({
"time": 123456,
"channel": "futures.tickers",
"event": "subscribe",
"payload": ["BTC_USD", "ETH_USD"]
});


socket.send(package);
});

// Send data from socket to all open tabs.
socket.addEventListener('message', ({ data }) => {
const package = JSON.parse(data);
connectedPorts.forEach(port => port.postMessage(package));
});

/**
* When a new thread is connected to the shared worker,
* start listening for messages from the new thread.
*/
self.addEventListener('connect', ({ ports }) => {
const port = ports[0];

// Add this new port to the list of connected ports.
connectedPorts.push(port);

/**
* Receive data from main thread and determine which
* actions it should take based on the received data.
*/
port.addEventListener('message', ({ data }) => {
const { action, value } = data;

// Send message to socket.
if (action === 'send') {
socket.send(JSON.stringify(value));

// Remove port from connected ports list.
} else if (action === 'unload') {
const index = connectedPorts.indexOf(port);
connectedPorts.splice(index, 1);
}
});

Main.js这只是filter_json_run_all的一部分,它会持续大约6或7个表格&Dygraph对象。我想介绍一下使用SetTimeout()等调用的一些操作

function filter_json_run_all(json_str){
const startTime = performance.now();
const data_in_array = json_str //JSON.parse(json_str.data);

// if ('DATETIME' in data_in_array){


//     var milliseconds = (new Date()).getTime() - Date.parse(data_in_array['DATETIME']);
//     console.log("milliseconds: " + milliseconds);
// }

if (summary in data_in_array){

if("DATETIME" in data_in_array){
var time_str = data_in_array["DATETIME"];
element_time.innerHTML = time_str;
}

// summary Data
const summary_array = data_in_array[summary];
var old_sum_arr_krw = [];
var old_sum_arr_irn = [];
var old_sum_arr_ntn = [];
var old_sum_arr_ccn = [];
var old_sum_arr_ihn = [];
var old_sum_arr_ppn = [];




var filtered_array_krw_summary = filterByProperty_summary(summary_array, "KWN")
old_sum_arr_krw.unshift(Table_summary_krw.getData());
Table_summary_krw.replaceData(filtered_array_krw_summary);
//Colour table
color_table(filtered_array_krw_summary, old_sum_arr_krw, Table_summary_krw);

var filtered_array_irn_summary = filterByProperty_summary(summary_array, "IRN")
old_sum_arr_irn.unshift(Table_summary_inr.getData());
Table_summary_inr.replaceData(filtered_array_irn_summary);
//Colour table
color_table(filtered_array_irn_summary, old_sum_arr_irn, Table_summary_inr);

var filtered_array_ntn_summary = filterByProperty_summary(summary_array, "NTN")
old_sum_arr_ntn.unshift(Table_summary_twd.getData());
Table_summary_twd.replaceData(filtered_array_ntn_summary);
//Colour table
color_table(filtered_array_ntn_summary, old_sum_arr_ntn, Table_summary_twd);

// remove formatting on fwds curves
setTimeout(() => {g_fwd_curve_krw.updateOptions({
'file': dataFwdKRW,
'labels': ['Time', 'Bid', 'Ask'],
strokeWidth: 1,
}); }, 200);
setTimeout(() => {g_fwd_curve_inr.updateOptions({
'file': dataFwdINR,
'labels': ['Time', 'Bid', 'Ask'],
strokeWidth: 1,
}); }, 200);
// remove_colors //([askTable_krw, askTable_inr, askTable_twd, askTable_cny, askTable_idr, askTable_php])              
setTimeout(() => {  askTable_krw.getRows().forEach(function (item, index) {
row = item.getCells();
row.forEach(function (value_tmp){value_tmp.getElement().style.backgroundColor = '';}
)}); }, 200);
setTimeout(() => {  askTable_inr.getRows().forEach(function (item, index) {
row = item.getCells();
row.forEach(function (value_tmp){value_tmp.getElement().style.backgroundColor = '';}
)}); }, 200);

color_table函数

function color_table(new_arr, old_array, table_obj){
// If length is not equal
if(new_arr.length!=old_array[0].length)
console.log("Diff length");
else
{

// Comparing each element of array
for(var i=0;i<new_arr.length;i++)
//iterate old dict dict
for (const [key, value] of Object.entries(old_array[0][i])) {

if(value == new_arr[i][key])
{}
else{
// console.log("Different element");
if(key!="TENOR")
// console.log(table_obj)
table_obj.getRows()[i].getCell(key).getElement().style.backgroundColor = 'yellow';
if(key!="TIME")
if(value < new_arr[i][key])
//green going up
//text_to_speech(new_arr[i]['CCY'] + ' ' +new_arr[i]['TENOR']+ ' getting bid')
table_obj.getRows()[i].getCell(key).getElement().style.backgroundColor = 'Chartreuse';
if(key!="TIME")
if(value > new_arr[i][key])
//red going down
table_obj.getRows()[i].getCell(key).getElement().style.backgroundColor = 'Crimson';
}
           
}
}
}

潜在的软糖/解决方案,谢谢Aaron:):

function limiter(fn, wait){
let isCalled = false,
calls = [];
let caller = function(){
if (calls.length && !isCalled){
isCalled = true;
if (calls.length >2){
calls.splice(0,calls.length-1)
//remove zero' upto n-1 function calls from array/ queue
}
calls.shift().call();
setTimeout(function(){
isCalled = false;
caller();
}, wait);
}
};
return function(){
calls.push(fn.bind(this, ...arguments));
// let args = Array.prototype.slice.call(arguments);
// calls.push(fn.bind.apply(fn, [this].concat(args)));
caller();
};
}

然后将其定义为网络工作者调用的常量:

const filter_json_run_allLimited = limiter(data => { filter_json_run_all(data); }, 300); // 300ms for examples

当新的Web套接字数据到达时,Web工作者调用受限函数:

// Event to listen for incoming data from the worker and update the DOM.
webSocketWorker.port.addEventListener('message', ({ data }) => {
// Limited function 
filter_json_run_allLimited(data);



});

如果有人知道tradingview或实时高性能数据流网站如何实现低延迟可视化更新,请发表评论,回复如下:)

在不知道color_table中发生了什么的情况下,我不愿尝试真正回答这个问题。根据您描述的行为,我的预感filter_json_run_all被迫等待拥挤的DOM操作/渲染管道,因为HTML正在更新,以实现更新后的表元素的颜色编码。

我看到您已经采取了一些措施来防止其中一些DOM操作阻塞该函数的执行(通过setTimeout)。如果color_table还没有采用类似的策略,那么这将是我关注重构的第一件事。

还值得将这些已处理事件的DOM更新放入一个简单的队列中,这样,如果浏览器行为缓慢会创建渲染囤积,那么实际负责调用挂起的DOM操作的函数可以选择跳过过时的渲染操作,以保持UI的可接受性。

编辑:一个基本的排队系统可能包括以下组件:

  1. 队列本身(这可以是一个简单的数组,只需要下面的两个组件都可以访问它)
  2. 队列附加器在filter_json_run_all期间运行,它只需将对象添加到队列的末尾,表示您计划使用color_table或setTimeout回调之一完成的每个DOM操作作业。这些对象应该包含要执行的操作(即:未调用的函数定义)和该操作的参数(即:传递到每个函数的参数)
  3. 队列运行器在自己的间隔上运行,并从队列的前面调用挂起的DOM操作任务,并在执行过程中删除它们。由于此操作可以访问队列中的所有对象,因此它还可以采取步骤优化/组合类似的操作,以最大限度地减少在执行后续代码之前要求浏览器重新绘制的量。例如,如果有多个color_table操作对同一单元格进行多次着色,则只需对涉及该单元格的队列中最后一个color_table项的数据执行一次此操作即可。此外,您可以通过在requestAnimationFrame回调中调用聚合的DOM操作本身来进一步优化与DOM的交互,这将确保只有在浏览器准备就绪时才会进行计划的回流/重新绘制,从性能角度来看,这比通过setTimeout/setInterval进行DOM操作排队更可取

最新更新