| /* |
| * 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 axios from 'axios'; |
| import { CancelToken } from 'axios'; |
| import debounce from 'lodash.debounce'; |
| |
| import { QueryState } from './query-state'; |
| |
| export interface QueryManagerOptions<Q, R> { |
| processQuery: ( |
| query: Q, |
| cancelToken: CancelToken, |
| setIntermediateQuery: (intermediateQuery: any) => void, |
| ) => Promise<R>; |
| onStateChange?: (queryResolve: QueryState<R>) => void; |
| debounceIdle?: number; |
| debounceLoading?: number; |
| } |
| |
| export class QueryManager<Q, R> { |
| private processQuery: ( |
| query: Q, |
| cancelToken: CancelToken, |
| setIntermediateQuery: (intermediateQuery: any) => void, |
| ) => Promise<R>; |
| private onStateChange?: (queryResolve: QueryState<R>) => void; |
| |
| private terminated = false; |
| private nextQuery: Q | undefined; |
| private lastQuery: Q | undefined; |
| private lastIntermediateQuery: any; |
| private currentRunCancelFn: (() => void) | undefined; |
| private state: QueryState<R> = QueryState.INIT; |
| 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: QueryState<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; |
| |
| if (this.currentRunCancelFn) { |
| this.currentRunCancelFn(); |
| } |
| const cancelToken = new axios.CancelToken(cancelFn => { |
| this.currentRunCancelFn = cancelFn; |
| }); |
| this.processQuery(this.lastQuery, cancelToken, (intermediateQuery: any) => { |
| this.lastIntermediateQuery = intermediateQuery; |
| }).then( |
| data => { |
| if (this.currentQueryId !== myQueryId) return; |
| this.currentRunCancelFn = undefined; |
| this.setState( |
| new QueryState<R>({ |
| data, |
| }), |
| ); |
| }, |
| (e: any) => { |
| if (this.currentQueryId !== myQueryId) return; |
| this.currentRunCancelFn = undefined; |
| if (axios.isCancel(e)) { |
| e = new Error(`canceled.`); // ToDo: fix! |
| } |
| this.setState( |
| new QueryState<R>({ |
| error: e, |
| }), |
| ); |
| }, |
| ); |
| } |
| |
| private trigger() { |
| const currentlyLoading = Boolean(this.currentRunCancelFn); |
| |
| this.setState( |
| new QueryState<R>({ |
| loading: true, |
| }), |
| ); |
| |
| if (currentlyLoading) { |
| 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 cancelCurrent(): void { |
| if (!this.currentRunCancelFn) return; |
| this.currentRunCancelFn(); |
| this.currentRunCancelFn = undefined; |
| } |
| |
| public getLastQuery(): Q | undefined { |
| return this.lastQuery; |
| } |
| |
| public getLastIntermediateQuery(): any { |
| return this.lastIntermediateQuery; |
| } |
| |
| public getState(): QueryState<R> { |
| return this.state; |
| } |
| |
| public terminate(): void { |
| this.terminated = true; |
| } |
| } |