在进度事件上处理分块JSON?

时间:2018-07-09 13:43:27

标签: javascript json d3.js xmlhttprequest chunked-encoding

我有一个服务器请求,该请求可能会返回巨大的json列表(〜100K记录,〜50 Mb),这些点我必须使用D3js在画布上绘制。我想在它们到达时绘制它们,以促进交互性和备用内存,所以:

我在服务器端启用了块传输编码 +我在客户端尝试过:

d3.json('?json=qDefects&operationid=' + opid) // my request 
  .on("load", function (json) {
    draw(json); // this works, but only after a long delay that I'd avoid...
  })
  .on("progress", function (json) {
    draw(json); // but this fails : json is not yet available here
  })
  .get();

在加载JSON时是否可以分块处理JSON?是否有助于以不同的方式构造JSON数据?当前它是单个数组,所以我有

[{"x":1, "y":2},{"x":2, "y":3}, // chunk 1
...
{"x":6845, "y":239426},{"x":51235, "y":234762}] // last chunk

将点分成较小的阵列是否有帮助?

3 个答案:

答案 0 :(得分:0)

tl; dr :您无法使用progress事件来操作JSON。


首先,您可能正在使用d3.request(D3 v3和v4),而不是d3.fetch(D3 v5)。这是一个重要的区别,因为在这两个微库中,该方法具有相同的名称,即d3.json。但是,d3.json在前者中是XMLHttpRequest,而在后者中是Promise。

第二,这是最重要的,(不幸的是)这是XY problem。您说过“我想在它们到来时画出它们,以促进交互性和备用内存” ,但问题是您不能:即使在XHR(或Promise)下载完数据后,D3只会在 之后开始绘制任何数据(您无法看到,请参见下文)。这意味着,在拥有50MB数据的情况下,用户将凝视空白页几秒钟……因此,此处的最佳建议是重新考虑数据文件和整个datavis的大小。

回到问题:

progress事件仅用于监视进度。根据{{​​3}}:

  

此规范定义了一个事件接口(ProgressEvent),可用于测量进度。 (强调我的)

我们可以在下面的演示中对此进行检查(我将数组与您在问题中共享的对象一起使用,我只是多次复制/粘贴了相同的对象)。我们可以使用srcElement.response来查看已加载的JSON,但是我们无法更改它:

d3.json("https://api.myjson.com/bins/1d7yoi")
  .on("progress", function(d) {
    console.log(d.srcElement.response)
  })
  .on("load", function() {
    console.log("done")
  })
  .get()
<script src="https://d3js.org/d3.v4.min.js"></script>

例如,您可以看到在这种愚蠢的尝试中更改了字符串中的任何内容,没有任何改变:

d3.json("https://api.myjson.com/bins/1d7yoi")
  .on("progress", function(d) {
    d.srcElement.response[0] = "foo";
    console.log("First character is: " + d.srcElement.response[0])
  })
  .on("load", function(data) {
    console.log("JSON:" + JSON.stringify(data))
  })
  .get()
<script src="https://d3js.org/d3.v4.min.js"></script>

答案 1 :(得分:0)

请参阅随附的小提琴:http://jsfiddle.net/Q5Jag/12412/

虽然上一个答案是正确的,因为您不能修改progress事件,但是您可以执行调用外部变量的简单操作。因此,以下代码将允许您重新处理字符串并将其发送到d3

var x = ''
d3.json("https://api.myjson.com/bins/1d7yoi")
  .on("progress", function(d) {
    x = d.responseText
    x = "ehllo" + x;
    console.log(x)
  })
  .on("load", function() {
    console.log("done")
  })
  .get()

您可以将responseText分配给变量x并根据需要在x上进行操作。

答案 2 :(得分:0)

由于之前的回答,我得出了以下结论:

function progressLoad(f) {
  let start = 0;
  return function (event) {
    let str = event.responseText.substr(start); 
    let i = str.indexOf("{");                  
    let j = str.lastIndexOf("}");
    str = "[" + str.substr(i, j) + "]";
    let data = JSON.parse(str);
    f(data);
    start = start + j + 1;
  }
}

d3.json('?json=qDefects&operationid=' + opid)
  .on("progress", progressLoad(draw));

在我没有嵌套{}的(简单)情况下,它工作得很好。 但是,我还确保了我的服务器提供了与请求的每个记录相对应的块,并且看起来responseText也随着这些块而增加,因此我在str中始终具有匹配的{}。

当然,这仍然会建立一个很长的无用的responseText,甚至可能是无用的最终json解析(即使我没有“ loaded”事件?),但我现在可以解决这个问题。