Skip to content

Commit

Permalink
feat(fetch): automatically cancel on going request on unmount (#516)
Browse files Browse the repository at this point in the history
* feat(fetch): automatically cancel on going request on unmount

* chore: don't call cancel if there's no promise
  • Loading branch information
pikax committed Aug 15, 2020
1 parent fdb93fd commit a030179
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 7 deletions.
5 changes: 5 additions & 0 deletions docs/composable/web/fetch.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ interface UseFetchOptions {
* @default true
*/
parseImmediate?: boolean;
/**
* @description cancels the request on component unmount
* @default true
*/
unmountCancel?: boolean;
}

```
Expand Down
39 changes: 38 additions & 1 deletion packages/vue-composable/__tests__/web/fetch.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useFetch } from "../../src";
import { promisedTimeout } from "../../src/utils";
import { nextTick } from "../utils";
import { nextTick, createVue } from "../utils";
import { Ref } from "../../src/api";

describe("fetch", () => {
let fetchSpy: jest.SpyInstance = undefined as any;
Expand Down Expand Up @@ -343,4 +344,40 @@ describe("fetch", () => {
"Cannot cancel because no request has been made"
);
});

it("should cancel on unmount", () => {
let isCancelled: Ref<boolean> = undefined as any;

const { mount, destroy } = createVue({
template: `<div></div>`,
setup() {
isCancelled = useFetch("./api").isCancelled;
}
});

mount();

expect(isCancelled.value).toBe(false);

destroy();
expect(isCancelled.value).toBe(true);
});

it("should not cancel on unmount if there's no promise", () => {
let isCancelled: Ref<boolean> = undefined as any;

const { mount, destroy } = createVue({
template: `<div></div>`,
setup() {
isCancelled = useFetch().isCancelled;
}
});

mount();

expect(isCancelled.value).toBe(false);

destroy();
expect(isCancelled.value).toBe(false);
});
});
36 changes: 30 additions & 6 deletions packages/vue-composable/src/web/fetch.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { isBoolean, isString } from "../utils";
import { ref, computed, Ref } from "../api";
import { ref, computed, Ref, onUnmounted, getCurrentInstance } from "../api";
import { PromiseResultFactory, usePromise } from "../promise";

export interface UseFetchOptions {
Expand All @@ -13,6 +13,12 @@ export interface UseFetchOptions {
* @default true
*/
parseImmediate?: boolean;

/**
* @description cancels the request on component unmount
* @default true
*/
unmountCancel?: boolean;
}

type ExtractArguments<T = any> = T extends (...args: infer TArgs) => void
Expand All @@ -38,7 +44,12 @@ interface FetchReturn<T>
}

function isFetchOptions(v: any): v is UseFetchOptions {
return v && (isBoolean(v.isJson) || isBoolean(v.parseImmediate));
return (
v &&
(isBoolean(v.isJson) ||
isBoolean(v.parseImmediate) ||
isBoolean(v.unmountCancel))
);
}

export function useFetch<T = any>(
Expand All @@ -58,14 +69,19 @@ export function useFetch<T = any>(

const jsonError = ref<any | null>(null);

const [isJson, parseImmediate] = isFetchOptions(options)
? [options.isJson !== false, options.parseImmediate !== false]
const [isJson, parseImmediate, unmountCancel] = isFetchOptions(options)
? [
options.isJson !== false,
options.parseImmediate !== false,
options.unmountCancel !== false
]
: isFetchOptions(requestInitOptions)
? [
requestInitOptions.isJson !== false,
requestInitOptions.parseImmediate !== false
requestInitOptions.parseImmediate !== false,
requestInitOptions.unmountCancel !== false
]
: [true, true];
: [true, true, true];

const requestInit = options
? isString(options)
Expand Down Expand Up @@ -155,6 +171,14 @@ export function useFetch<T = any>(
}
}

if (unmountCancel && getCurrentInstance()) {
onUnmounted(() => {
if (abortController) {
cancel("unmounted");
}
});
}

return {
...use,

Expand Down

0 comments on commit a030179

Please sign in to comment.