blob: d7f0acbf30aa6e67dea3354dd45335e8f84ffa6c [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import debounce from 'lodash.debounce';
export interface QueryStateInt<R> {
result?: R;
loading: boolean;
error?: string;
}
export interface QueryManagerOptions<Q, R> {
processQuery: (query: Q, setIntermediateQuery: (intermediateQuery: any) => void) => Promise<R>;
onStateChange?: (queryResolve: QueryStateInt<R>) => void;
debounceIdle?: number;
debounceLoading?: number;
}
export class QueryManager<Q, R> {
private processQuery: (
query: Q,
setIntermediateQuery: (intermediateQuery: any) => void,
) => Promise<R>;
private onStateChange?: (queryResolve: QueryStateInt<R>) => void;
private terminated = false;
private nextQuery: Q | undefined;
private lastQuery: Q | undefined;
private lastIntermediateQuery: any;
private actuallyLoading = false;
private state: QueryStateInt<R> = {
loading: false,
};
private currentQueryId = 0;
private runWhenIdle: () => void;
private runWhenLoading: () => void;
constructor(options: QueryManagerOptions<Q, R>) {
this.processQuery = options.processQuery;
this.onStateChange = options.onStateChange;
if (options.debounceIdle !== 0) {
this.runWhenIdle = debounce(this.run, options.debounceIdle || 100);
} else {
this.runWhenIdle = this.run;
}
if (options.debounceLoading !== 0) {
this.runWhenLoading = debounce(this.run, options.debounceLoading || 200);
} else {
this.runWhenLoading = this.run;
}
}
private setState(queryState: QueryStateInt<R>) {
this.state = queryState;
if (this.onStateChange && !this.terminated) {
this.onStateChange(queryState);
}
}
private run() {
this.lastQuery = this.nextQuery;
if (typeof this.lastQuery === 'undefined') return;
this.currentQueryId++;
const myQueryId = this.currentQueryId;
this.actuallyLoading = true;
this.processQuery(this.lastQuery, (intermediateQuery: any) => {
this.lastIntermediateQuery = intermediateQuery;
}).then(
result => {
if (this.currentQueryId !== myQueryId) return;
this.actuallyLoading = false;
this.setState({
result,
loading: false,
error: undefined,
});
},
(e: Error) => {
if (this.currentQueryId !== myQueryId) return;
this.actuallyLoading = false;
this.setState({
result: undefined,
loading: false,
error: e.message,
});
},
);
}
private trigger() {
const currentActuallyLoading = this.actuallyLoading;
this.setState({
result: undefined,
loading: true,
error: undefined,
});
if (currentActuallyLoading) {
this.runWhenLoading();
} else {
this.runWhenIdle();
}
}
public runQuery(query: Q): void {
if (this.terminated) return;
this.nextQuery = query;
this.trigger();
}
public rerunLastQuery(runInBackground = false): void {
if (this.terminated) return;
this.nextQuery = this.lastQuery;
if (runInBackground) {
this.runWhenIdle();
} else {
this.trigger();
}
}
public getLastQuery(): Q | undefined {
return this.lastQuery;
}
public getLastIntermediateQuery(): any {
return this.lastIntermediateQuery;
}
public getState(): QueryStateInt<R> {
return this.state;
}
public terminate(): void {
this.terminated = true;
}
}