Track multiple xhrs per URL rather than overriding

It was possible for a url to be overridden then canceled, leaving the
open connection open and forgotten about.
This commit is contained in:
Michael Lange 2018-03-29 11:44:26 -07:00
parent b23bc70bce
commit a226021a05
2 changed files with 61 additions and 15 deletions

View File

@ -10,7 +10,27 @@ export default ApplicationAdapter.extend({
store: service(),
xhrs: computed(function() {
return {};
return {
list: {},
track(key, xhr) {
if (this.list[key]) {
this.list[key].push(xhr);
} else {
this.list[key] = [xhr];
}
},
cancel(key) {
while (this.list[key] && this.list[key].length) {
this.remove(key, this.list[key][0]);
}
},
remove(key, xhr) {
if (this.list[key]) {
xhr.abort();
this.list[key].removeObject(xhr);
}
},
};
}),
ajaxOptions() {
@ -22,9 +42,9 @@ export default ApplicationAdapter.extend({
if (previousBeforeSend) {
previousBeforeSend(...arguments);
}
this.get('xhrs')[key] = jqXHR;
this.get('xhrs').track(key, jqXHR);
jqXHR.always(() => {
delete this.get('xhrs')[key];
this.get('xhrs').remove(key, jqXHR);
});
};
@ -127,10 +147,7 @@ export default ApplicationAdapter.extend({
return;
}
const url = this.urlForFindRecord(id, modelName);
const xhr = this.get('xhrs')[url];
if (xhr) {
xhr.abort();
}
this.get('xhrs').cancel(url);
},
cancelFindAll(modelName) {
@ -142,10 +159,7 @@ export default ApplicationAdapter.extend({
if (params) {
url = `${url}?${params}`;
}
const xhr = this.get('xhrs')[url];
if (xhr) {
xhr.abort();
}
this.get('xhrs').cancel(url);
},
cancelReloadRelationship(model, relationshipName) {
@ -159,10 +173,7 @@ export default ApplicationAdapter.extend({
);
} else {
const url = model[relationship.kind](relationship.key).link();
const xhr = this.get('xhrs')[url];
if (xhr) {
xhr.abort();
}
this.get('xhrs').cancel(url);
}
},
});

View File

@ -257,6 +257,41 @@ test('relationship reloads can be canceled', function(assert) {
});
});
test('requests can be canceled even if multiple requests for the same URL were made', function(assert) {
const { pretender } = this.server;
const jobId = JSON.stringify(['job-1', 'default']);
pretender.get('/v1/job/:id', () => [200, {}, '{}'], true);
this.subject().findRecord(null, { modelName: 'job' }, jobId, {
reload: true,
adapterOptions: { watch: true },
});
this.subject().findRecord(null, { modelName: 'job' }, jobId, {
reload: true,
adapterOptions: { watch: true },
});
const { request: xhr } = pretender.requestReferences[0];
assert.equal(xhr.status, 0, 'Request is still pending');
assert.equal(pretender.requestReferences.length, 2, 'Two findRecord requests were made');
assert.equal(
pretender.requestReferences.mapBy('url').uniq().length,
1,
'The two requests have the same URL'
);
// Schedule the cancelation before waiting
run.next(() => {
this.subject().cancelFindRecord('job', jobId);
});
return wait().then(() => {
assert.ok(xhr.aborted, 'Request was aborted');
});
});
function makeMockModel(id, options) {
return assign(
{