mirror of
https://github.com/gosticks/web.git
synced 2026-10-07 07:07:07 +00:00
Move cancelable promises to new file and add tests
Signed-off-by: Mcat12 <newtoncat12@yahoo.com>
This commit is contained in:
@@ -15,7 +15,10 @@ import { WithNamespaces, withNamespaces } from "react-i18next";
|
||||
import NavButton from "./NavButton";
|
||||
import NavDropdown from "./NavDropdown";
|
||||
import { StatusContext } from "./context/StatusContext";
|
||||
import { CancelablePromise, makeCancelable } from "../../util";
|
||||
import {
|
||||
CancelablePromise,
|
||||
makeCancelable
|
||||
} from "../../util/CancelablePromise";
|
||||
import api from "../../util/api";
|
||||
import {
|
||||
Button,
|
||||
|
||||
@@ -9,7 +9,11 @@
|
||||
* Please see LICENSE file for your rights under this license. */
|
||||
|
||||
import { Component, ReactNode } from "react";
|
||||
import { CancelablePromise, ignoreCancel, makeCancelable } from "../../util";
|
||||
import {
|
||||
CancelablePromise,
|
||||
ignoreCancel,
|
||||
makeCancelable
|
||||
} from "../../util/CancelablePromise";
|
||||
import { Err, Ok, Result } from "../../util/result";
|
||||
|
||||
export interface WithAPIDataProps<T> {
|
||||
|
||||
@@ -13,7 +13,11 @@ import { WithNamespaces, withNamespaces } from "react-i18next";
|
||||
import DomainInput from "./DomainInput";
|
||||
import Alert, { AlertType } from "../common/Alert";
|
||||
import DomainList from "./DomainList";
|
||||
import { CancelablePromise, ignoreCancel, makeCancelable } from "../../util";
|
||||
import {
|
||||
CancelablePromise,
|
||||
ignoreCancel,
|
||||
makeCancelable
|
||||
} from "../../util/CancelablePromise";
|
||||
|
||||
export interface ListPageProps extends WithNamespaces {
|
||||
title: string;
|
||||
|
||||
@@ -20,18 +20,18 @@ import i18next from "i18next";
|
||||
import { WithNamespaces, withNamespaces } from "react-i18next";
|
||||
import debounce from "lodash.debounce";
|
||||
import moment from "moment";
|
||||
import {
|
||||
CancelablePromise,
|
||||
ignoreCancel,
|
||||
makeCancelable,
|
||||
padNumber
|
||||
} from "../../util";
|
||||
import { padNumber } from "../../util";
|
||||
import api from "../../util/api";
|
||||
import { dateRanges } from "../../util/dateRanges";
|
||||
import { TranslatedTimeRangeSelector } from "../dashboard/TimeRangeSelector";
|
||||
import { TimeRange } from "../common/context/TimeRangeContext";
|
||||
import "react-table/react-table.css";
|
||||
import "bootstrap-daterangepicker/daterangepicker.css";
|
||||
import {
|
||||
CancelablePromise,
|
||||
ignoreCancel,
|
||||
makeCancelable
|
||||
} from "../../util/CancelablePromise";
|
||||
|
||||
export interface QueryLogState {
|
||||
history: Array<ApiQuery>;
|
||||
|
||||
@@ -10,7 +10,11 @@
|
||||
|
||||
import React, { ChangeEvent, Component, FormEvent } from "react";
|
||||
import { WithNamespaces, withNamespaces } from "react-i18next";
|
||||
import { CancelablePromise, ignoreCancel, makeCancelable } from "../../util";
|
||||
import {
|
||||
CancelablePromise,
|
||||
ignoreCancel,
|
||||
makeCancelable
|
||||
} from "../../util/CancelablePromise";
|
||||
import api from "../../util/api";
|
||||
import {
|
||||
Button,
|
||||
|
||||
@@ -10,7 +10,11 @@
|
||||
|
||||
import React, { Component, FormEvent } from "react";
|
||||
import { WithNamespaces, withNamespaces } from "react-i18next";
|
||||
import { CancelablePromise, ignoreCancel, makeCancelable } from "../../util";
|
||||
import {
|
||||
CancelablePromise,
|
||||
ignoreCancel,
|
||||
makeCancelable
|
||||
} from "../../util/CancelablePromise";
|
||||
import api from "../../util/api";
|
||||
import DnsList from "./DnsList";
|
||||
import { Button, Col, Form, FormGroup } from "reactstrap";
|
||||
|
||||
@@ -10,7 +10,11 @@
|
||||
|
||||
import React, { ChangeEvent, Component, FormEvent } from "react";
|
||||
import { WithNamespaces, withNamespaces } from "react-i18next";
|
||||
import { CancelablePromise, ignoreCancel, makeCancelable } from "../../util";
|
||||
import {
|
||||
CancelablePromise,
|
||||
ignoreCancel,
|
||||
makeCancelable
|
||||
} from "../../util/CancelablePromise";
|
||||
import api from "../../util/api";
|
||||
import Alert, { AlertType } from "../common/Alert";
|
||||
import { Button, Col, Form, FormGroup, Input, Label } from "reactstrap";
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
/* Pi-hole: A black hole for Internet advertisements
|
||||
* (c) 2019 Pi-hole, LLC (https://pi-hole.net)
|
||||
* Network-wide ad blocking via your own hardware.
|
||||
*
|
||||
* Web Interface
|
||||
* Wrap promises to make them cancelable
|
||||
*
|
||||
* This file is copyright under the latest version of the EUPL.
|
||||
* Please see LICENSE file for your rights under this license. */
|
||||
|
||||
/**
|
||||
* A promise which can be canceled
|
||||
*/
|
||||
export interface CancelablePromise<T> {
|
||||
promise: Promise<T>;
|
||||
cancel: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* The options given to {@link makeCancelable}
|
||||
*/
|
||||
export interface CancelableOptions {
|
||||
/**
|
||||
* The function to call to repeat the promise
|
||||
*/
|
||||
repeat: () => void;
|
||||
|
||||
/**
|
||||
* The amount of time to wait until repeating
|
||||
*/
|
||||
interval: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a promise cancelable and repeatable
|
||||
*
|
||||
* @param promise the promise
|
||||
* @param options the interval repeat options
|
||||
* @returns a cancelable promise
|
||||
*/
|
||||
export function makeCancelable<T>(
|
||||
promise: Promise<T>,
|
||||
options?: CancelableOptions
|
||||
): CancelablePromise<T> {
|
||||
let hasCanceled = false;
|
||||
let repeatId: NodeJS.Timeout | null = null;
|
||||
|
||||
const handle = (
|
||||
resolve: (value: T) => void,
|
||||
reject: (error: any) => void,
|
||||
val: T,
|
||||
isError: boolean
|
||||
) => {
|
||||
if (hasCanceled) {
|
||||
reject({ isCanceled: true });
|
||||
return;
|
||||
}
|
||||
|
||||
if (isError) {
|
||||
reject(val);
|
||||
} else {
|
||||
resolve(val);
|
||||
}
|
||||
|
||||
if (options) {
|
||||
repeatId = setTimeout(options.repeat, options.interval);
|
||||
}
|
||||
};
|
||||
|
||||
const wrappedPromise: Promise<T> = new Promise((resolve, reject) => {
|
||||
promise.then(
|
||||
val => handle(resolve, reject, val, false),
|
||||
error => handle(resolve, reject, error, true)
|
||||
);
|
||||
});
|
||||
|
||||
return {
|
||||
promise: wrappedPromise,
|
||||
cancel() {
|
||||
if (repeatId !== null) {
|
||||
clearTimeout(repeatId);
|
||||
}
|
||||
hasCanceled = true;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Ignore canceled promises (pass into a promise's catch function)
|
||||
*
|
||||
* @param err the error from catching the promise
|
||||
*/
|
||||
export const ignoreCancel = (err: any) => {
|
||||
if (!err.isCanceled) throw err;
|
||||
};
|
||||
@@ -0,0 +1,102 @@
|
||||
/* Pi-hole: A black hole for Internet advertisements
|
||||
* (c) 2019 Pi-hole, LLC (https://pi-hole.net)
|
||||
* Network-wide ad blocking via your own hardware.
|
||||
*
|
||||
* Web Interface
|
||||
* Tests for canceling promises
|
||||
*
|
||||
* This file is copyright under the latest version of the EUPL.
|
||||
* Please see LICENSE file for your rights under this license. */
|
||||
|
||||
import { makeCancelable, ignoreCancel } from "../CancelablePromise";
|
||||
|
||||
describe("makeCancelable", () => {
|
||||
const testValue = "test";
|
||||
const testError = "testError";
|
||||
const promise = Promise.resolve(testValue);
|
||||
const promiseErr = Promise.reject(testError);
|
||||
|
||||
it("passes through promises in the default case", async () => {
|
||||
const cancelablePromise = makeCancelable(promise);
|
||||
|
||||
await expect(cancelablePromise.promise).resolves.toEqual(testValue);
|
||||
});
|
||||
|
||||
it("passes through errors in the default case", async () => {
|
||||
const cancelablePromise = makeCancelable(promiseErr);
|
||||
|
||||
await expect(cancelablePromise.promise).rejects.toEqual(testError);
|
||||
});
|
||||
|
||||
it("rejects with cancel error if canceled", async () => {
|
||||
const cancelablePromise = makeCancelable(promise);
|
||||
|
||||
cancelablePromise.cancel();
|
||||
|
||||
await expect(cancelablePromise.promise).rejects.toEqual({
|
||||
isCanceled: true
|
||||
});
|
||||
});
|
||||
|
||||
it("calls the repeat function after resolving", async () => {
|
||||
jest.useFakeTimers();
|
||||
|
||||
const mockFunction = jest.fn();
|
||||
const interval = 1000;
|
||||
const cancelablePromise = makeCancelable(promise, {
|
||||
interval,
|
||||
repeat: mockFunction
|
||||
});
|
||||
|
||||
await expect(cancelablePromise.promise).resolves;
|
||||
|
||||
expect(setTimeout).toHaveBeenCalledWith(mockFunction, interval);
|
||||
});
|
||||
|
||||
it("calls the repeat function after rejecting", async () => {
|
||||
jest.useFakeTimers();
|
||||
|
||||
const mockFunction = jest.fn();
|
||||
const interval = 1000;
|
||||
const cancelablePromise = makeCancelable(promiseErr, {
|
||||
interval,
|
||||
repeat: mockFunction
|
||||
});
|
||||
|
||||
await expect(cancelablePromise.promise).rejects.toEqual(testError);
|
||||
|
||||
expect(setTimeout).toHaveBeenCalledWith(mockFunction, interval);
|
||||
});
|
||||
|
||||
it("clears the timeout if canceled after resolving", async () => {
|
||||
jest.useFakeTimers();
|
||||
|
||||
const mockFunction = jest.fn();
|
||||
const interval = 1000;
|
||||
const cancelablePromise = makeCancelable(promise, {
|
||||
interval,
|
||||
repeat: mockFunction
|
||||
});
|
||||
|
||||
await cancelablePromise.promise;
|
||||
|
||||
cancelablePromise.cancel();
|
||||
|
||||
expect(clearTimeout).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("ignoreCancel", () => {
|
||||
it("passes through non-canceled errors", async () => {
|
||||
const testError = "test";
|
||||
const promise = Promise.reject(testError);
|
||||
|
||||
await expect(promise.catch(ignoreCancel)).rejects.toEqual(testError);
|
||||
});
|
||||
|
||||
it("does not pass through canceled errors", async () => {
|
||||
const canceledPromise = Promise.reject({ isCanceled: true });
|
||||
|
||||
await expect(canceledPromise.catch(ignoreCancel)).resolves;
|
||||
});
|
||||
});
|
||||
@@ -29,77 +29,3 @@ export const padNumber = (num: number) => {
|
||||
export const getIntervalForRange = (range: TimeRange): number => {
|
||||
return Math.ceil((range.until.unix() - range.from.unix()) / 144);
|
||||
};
|
||||
|
||||
export interface CancelablePromise<T> {
|
||||
promise: Promise<T>;
|
||||
cancel: () => void;
|
||||
}
|
||||
|
||||
export interface CancelableOptions {
|
||||
/**
|
||||
* The function to call to repeat the promise
|
||||
*/
|
||||
repeat: () => void;
|
||||
|
||||
/**
|
||||
* The amount of time to wait until repeating
|
||||
*/
|
||||
interval: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a promise cancelable and repeatable
|
||||
*
|
||||
* @param promise the promise
|
||||
* @param options the interval repeat options
|
||||
* @returns {{promise: Promise<T>, cancel(): void}} a handle on the cancelable
|
||||
* promise
|
||||
*/
|
||||
export function makeCancelable<T>(
|
||||
promise: Promise<T>,
|
||||
options?: CancelableOptions
|
||||
): CancelablePromise<T> {
|
||||
let hasCanceled = false;
|
||||
let repeatId: NodeJS.Timeout | null = null;
|
||||
|
||||
const handle = (
|
||||
resolve: (value: any) => void,
|
||||
reject: (error: any) => void,
|
||||
val: T,
|
||||
isError: boolean
|
||||
) => {
|
||||
if (hasCanceled) reject({ isCanceled: true });
|
||||
else {
|
||||
if (isError) reject(val);
|
||||
else resolve(val);
|
||||
|
||||
if (options) repeatId = setTimeout(options.repeat, options.interval);
|
||||
}
|
||||
};
|
||||
|
||||
const wrappedPromise: Promise<T> = new Promise((resolve, reject) => {
|
||||
promise.then(
|
||||
val => handle(resolve, reject, val, false),
|
||||
error => handle(resolve, reject, error, true)
|
||||
);
|
||||
});
|
||||
|
||||
return {
|
||||
promise: wrappedPromise,
|
||||
cancel() {
|
||||
if (repeatId !== null) {
|
||||
clearTimeout(repeatId);
|
||||
}
|
||||
hasCanceled = true;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Ignore canceled promises (pass into a promise's catch function)
|
||||
*
|
||||
* @param err the error from catching the promise
|
||||
*/
|
||||
export const ignoreCancel = (err: any) => {
|
||||
if (!err.isCanceled) throw err;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user