* fix: #1075 Returning to the Study List before all series have finished loading throws multiple errors in console * fix: 🐛 Addressing review feedback from issue #1075 Adding support for cancelable pattern Closes: #1075 * fix: 🐛 update unit test for exports Closes: #1075
This commit is contained in:
parent
d26adf92bd
commit
ecaf578f92
@ -11,6 +11,7 @@ import writeScript from './writeScript.js';
|
|||||||
import DicomLoaderService from './dicomLoaderService.js';
|
import DicomLoaderService from './dicomLoaderService.js';
|
||||||
import b64toBlob from './b64toBlob.js';
|
import b64toBlob from './b64toBlob.js';
|
||||||
import * as urlUtil from './urlUtil';
|
import * as urlUtil from './urlUtil';
|
||||||
|
import makeCancelable from './makeCancelable';
|
||||||
|
|
||||||
const utils = {
|
const utils = {
|
||||||
guid,
|
guid,
|
||||||
@ -27,6 +28,7 @@ const utils = {
|
|||||||
DICOMTagDescriptions,
|
DICOMTagDescriptions,
|
||||||
DicomLoaderService,
|
DicomLoaderService,
|
||||||
urlUtil,
|
urlUtil,
|
||||||
|
makeCancelable,
|
||||||
};
|
};
|
||||||
|
|
||||||
export {
|
export {
|
||||||
@ -44,6 +46,7 @@ export {
|
|||||||
DICOMTagDescriptions,
|
DICOMTagDescriptions,
|
||||||
DicomLoaderService,
|
DicomLoaderService,
|
||||||
urlUtil,
|
urlUtil,
|
||||||
|
makeCancelable,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default utils;
|
export default utils;
|
||||||
|
|||||||
@ -17,6 +17,7 @@ describe('Top level exports', () => {
|
|||||||
'DICOMTagDescriptions',
|
'DICOMTagDescriptions',
|
||||||
'DicomLoaderService',
|
'DicomLoaderService',
|
||||||
'urlUtil',
|
'urlUtil',
|
||||||
|
'makeCancelable',
|
||||||
].sort();
|
].sort();
|
||||||
|
|
||||||
const exports = Object.keys(utils.default).sort();
|
const exports = Object.keys(utils.default).sort();
|
||||||
|
|||||||
19
platform/core/src/utils/makeCancelable.js
Normal file
19
platform/core/src/utils/makeCancelable.js
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
export default function makeCancelable(thenable) {
|
||||||
|
let isCanceled = false;
|
||||||
|
const promise = Promise.resolve(thenable).then(
|
||||||
|
function(result) {
|
||||||
|
if (isCanceled) throw Object.freeze({ isCanceled });
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
function(error) {
|
||||||
|
if (isCanceled) throw Object.freeze({ isCanceled, error });
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return Object.assign(Object.create(promise), {
|
||||||
|
then: promise.then.bind(promise),
|
||||||
|
cancel() {
|
||||||
|
isCanceled = true;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
@ -1,6 +1,7 @@
|
|||||||
/* global cornerstone */
|
/* global cornerstone */
|
||||||
import './ImageThumbnail.styl';
|
import './ImageThumbnail.styl';
|
||||||
|
|
||||||
|
import { utils } from '@ohif/core';
|
||||||
import React, { PureComponent } from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
|
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
@ -32,7 +33,7 @@ export default class ImageThumbnail extends PureComponent {
|
|||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
this.cancelablePromises = [];
|
||||||
this.canvas = React.createRef();
|
this.canvas = React.createRef();
|
||||||
this.state = {
|
this.state = {
|
||||||
loading: this.shouldRenderToCanvas(),
|
loading: this.shouldRenderToCanvas(),
|
||||||
@ -43,10 +44,17 @@ export default class ImageThumbnail extends PureComponent {
|
|||||||
return this.props.imageId && !this.props.imageSrc;
|
return this.props.imageId && !this.props.imageSrc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fetchImage() {
|
||||||
|
const cancelablePromise = utils.makeCancelable(
|
||||||
|
cornerstone.loadAndCacheImage(this.props.imageId)
|
||||||
|
);
|
||||||
|
this.cancelablePromises.push(cancelablePromise);
|
||||||
|
return cancelablePromise;
|
||||||
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
if (this.shouldRenderToCanvas()) {
|
if (this.shouldRenderToCanvas()) {
|
||||||
cornerstone
|
this.fetchImage()
|
||||||
.loadAndCacheImage(this.props.imageId)
|
|
||||||
.then(image => {
|
.then(image => {
|
||||||
cornerstone.renderToCanvas(this.canvas.current, image);
|
cornerstone.renderToCanvas(this.canvas.current, image);
|
||||||
this.setState({
|
this.setState({
|
||||||
@ -54,6 +62,7 @@ export default class ImageThumbnail extends PureComponent {
|
|||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
|
if (error.isCanceled) return;
|
||||||
this.setState({
|
this.setState({
|
||||||
loading: false,
|
loading: false,
|
||||||
error: true,
|
error: true,
|
||||||
@ -63,6 +72,12 @@ export default class ImageThumbnail extends PureComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
while (this.cancelablePromises.length > 0) {
|
||||||
|
this.cancelablePromises.pop().cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
let loadingOrError;
|
let loadingOrError;
|
||||||
if (this.props.error) {
|
if (this.props.error) {
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import PropTypes from 'prop-types';
|
|||||||
import { extensionManager } from './../App.js';
|
import { extensionManager } from './../App.js';
|
||||||
|
|
||||||
const { OHIFStudyMetadata, OHIFSeriesMetadata } = metadata;
|
const { OHIFStudyMetadata, OHIFSeriesMetadata } = metadata;
|
||||||
const { retrieveStudiesMetadata } = studies;
|
const { retrieveStudiesMetadata, deleteStudyMetadataPromise } = studies;
|
||||||
const { studyMetadataManager, updateMetaDataManager } = utils;
|
const { studyMetadataManager, updateMetaDataManager } = utils;
|
||||||
|
|
||||||
class ViewerRetrieveStudyData extends Component {
|
class ViewerRetrieveStudyData extends Component {
|
||||||
@ -18,6 +18,8 @@ class ViewerRetrieveStudyData extends Component {
|
|||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
this.abortSeriesLoad = false;
|
||||||
|
this.seriesLoadStats = Object.create(null);
|
||||||
this.state = {
|
this.state = {
|
||||||
studies: null,
|
studies: null,
|
||||||
error: null,
|
error: null,
|
||||||
@ -83,18 +85,49 @@ class ViewerRetrieveStudyData extends Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_handleSeriesLoadResult(error, studyMetadata, series) {
|
||||||
|
if (this.abortSeriesLoad) return;
|
||||||
|
const stats = this.seriesLoadStats[studyMetadata.getStudyInstanceUID()];
|
||||||
|
if (!stats) return;
|
||||||
|
stats.count--;
|
||||||
|
if (error || !series) {
|
||||||
|
stats.errors++;
|
||||||
|
log.error(error || 'Bad Series');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._addSeriesToStudy(studyMetadata, series);
|
||||||
|
}
|
||||||
|
|
||||||
_attemptToLoadRemainingSeries(studyMetadata) {
|
_attemptToLoadRemainingSeries(studyMetadata) {
|
||||||
const { seriesLoader } = studyMetadata.getData();
|
const { seriesLoader } = studyMetadata.getData();
|
||||||
if (!seriesLoader) {
|
if (!seriesLoader) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const stats = (this.seriesLoadStats[studyMetadata.getStudyInstanceUID()] = {
|
||||||
|
errors: 0,
|
||||||
|
count: 0,
|
||||||
|
});
|
||||||
while (seriesLoader.hasNext()) {
|
while (seriesLoader.hasNext()) {
|
||||||
seriesLoader
|
seriesLoader
|
||||||
.next()
|
.next()
|
||||||
.then(
|
.then(
|
||||||
series => void this._addSeriesToStudy(studyMetadata, series),
|
series =>
|
||||||
error => void log.error(error)
|
void this._handleSeriesLoadResult(null, studyMetadata, series),
|
||||||
|
error => void this._handleSeriesLoadResult({ error }, null, null)
|
||||||
);
|
);
|
||||||
|
stats.count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
this.abortSeriesLoad = true;
|
||||||
|
for (const studyInstanceUid in this.seriesLoadStats) {
|
||||||
|
const stats = this.seriesLoadStats[studyInstanceUid];
|
||||||
|
if (stats && (stats.count > 0 || stats.errors > 0)) {
|
||||||
|
deleteStudyMetadataPromise(studyInstanceUid);
|
||||||
|
studyMetadataManager.remove(studyInstanceUid);
|
||||||
|
log.info(`Purging incomplete study data: ${studyInstanceUid}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user