d3 / d3-request

A convenient alternative to XMLHttpRequest.
BSD 3-Clause "New" or "Revised" License
110 stars 54 forks source link

Added `onabort` to XMLHttpRequest event listeners #18

Closed sfinktah closed 3 years ago

sfinktah commented 7 years ago

So, I had this code which failed to indicate completion when an xhr request was aborted. Ergo, I had to add onabort. I couldn't see a super-tidy way of integrating it, so I just left it to default as an error, which can (again by default) be detected by checking error.type === 'abort'

Simplified code sample, which is working great now.

function setupCompletionHandler(selection) {
    counter = selection.size();
}

function onTileComplete() {
    if (--counter === 0)
        self.emit('complete');
}

image.exit()
    .each(function(d) { this._xhr && this._xhr.abort(); });

image.enter()
    .call(setupCompletionHandler)
    .append("svg")
    .each(function(d) {
        this._xhr = d3.request(url)
            .get(function(error, data) {
                onTileComplete();
                if (error)
                    if (error.type !== 'abort')
                        throw error;
                process(data);
            });
    });

If there's a better way to do this, I'm all ears. I could see it being possible with d3-queue, but ideally a simple completion event would be delightful. The solution I arrived at was found at SO.

mbostock commented 7 years ago

The main reason abort doesn’t dispatch an error is that abort is only triggered programmatically (i.e., by you), not by the request (externally). So anytime you call request.abort, you could manually trigger whatever side-effect you wish to happen. Here’s some untested code that hopefully conveys this approach:

function setupCompletionHandler(selection) {
  counter = selection.size();
}

function onTileComplete() {
  delete this._xhr;
  if (--counter === 0) {
    self.emit('complete');
  }
}

image.exit()
    .each(function(d) {
      if (this._xhr) {
        this._xhr.abort();
        onTileComplete.call(this);
      }
    });

image.enter()
    .call(setupCompletionHandler)
    .append("svg")
    .each(function(d) {
      var that = this;
      that._xhr = d3.request(url, function(error, data) {
        onTileComplete.call(that);
        if (error) throw error;
        process(data);
      });
    });
sfinktah commented 3 years ago

IIRC, I added this PR during a project which involved shadowing map tiles from two different map-tile providers. When the map moved, XHR connections were broken, but not necessarily in a fashion that would have allowed tidy integration of a clean-up code (though really, I have no actual recollection). What I do recall was that it was a tremendous pain to setup. :)

That was a long time ago, and I am no longer working on that project.

You would obviously have a fatter better grasp on the overall situation than me, even had my D3 experience been so much years ago. I appreciate you taking the time to offer your advice on the matter. I can but offer my meagre PRs when I have the opportunity to give something back, and leave it to the authors to decide whether they are of any use.