Add fetch concurrency limit on obs annotation loading (#1318)

* add fetch concurrency limit

* add tests for PromiseLimit
This commit is contained in:
Bruce Martin
2020-03-30 16:23:09 -07:00
committed by GitHub
parent 033727632c
commit 708a5af039
3 changed files with 137 additions and 12 deletions
+14 -12
View File
@@ -7,6 +7,7 @@ import {
doBinaryRequest,
dispatchNetworkErrorMessageToUser
} from "../util/actionHelpers";
import { PromiseLimit } from "../util/promiseLimit";
import { requestReembed, reembedResetWorldToUniverse } from "./reembed";
/*
@@ -15,28 +16,29 @@ we don't need.
*/
function obsAnnotationFetchAndLoad(dispatch, schema) {
const obsAnnotations = schema?.schema?.annotations?.obs ?? {};
const columns = obsAnnotations.columns ?? [];
const index = obsAnnotations.index ?? false;
const columns = (obsAnnotations.columns ?? []).filter(
col => col.name !== index
);
const plimit = new PromiseLimit(4);
return Promise.all(
columns
.filter(col => col.name !== index)
.map(col => {
columns.map(col =>
plimit.add(() => {
const path = `annotations/obs?annotation-name=${encodeURIComponent(
col.name
)}`;
const url = `${globals.API.prefix}${globals.API.version}${path}`;
return doBinaryRequest(url);
})
.map(rqst => rqst.then(buffer => Universe.matrixFBSToDataframe(buffer)))
.map(resp =>
resp.then(df =>
return doBinaryRequest(url).then(buffer => {
const df = Universe.matrixFBSToDataframe(buffer);
dispatch({
type: "universe: column load success",
dim: "obsAnnotations",
dataframe: df
})
)
)
});
});
})
)
);
}
+50
View File
@@ -0,0 +1,50 @@
/*
This class provides a means to resolve Promises asynchronously,
and limit the number concurrently executed. For example, if
you want to involve a large number of API endpoints using fetch(),
but want to limit the number simultaneously outstanding.
Example usage:
const plimit = new PromiseLimit(2);
return Promise.all([
plimit.add(() => fetch('/foo')),
plimit.add(() => fetch('/bar')),
plimit.add(() => fetch('/baz'))
])
*/
export class PromiseLimit {
constructor(maxConcurrency) {
this.queue = new Set();
this.maxConcurrency = maxConcurrency;
this.pending = 0;
}
add(fn, ...args) {
// fn - must return a promise
// args - will be passed to fn
return new Promise((resolve, reject) => {
this.queue.add({ fn, args, resolve, reject });
this._resolveNext(false);
});
}
_resolveNext = (completed = true) => {
if (completed) this.pending -= 1;
while (this.queue.size > 0 && this.pending < this.maxConcurrency) {
const task = this.queue.values().next().value; // order of insertion
this.pending += 1;
this.queue.delete(task);
const { resolve, reject, fn, args } = task;
try {
const result = fn(...args);
result.then(this._resolveNext, this._resolveNext);
result.then(resolve, reject);
} catch (err) {
reject(err);
}
}
};
}