Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

real-cancellable-promise

Package Overview
Dependencies
Maintainers
2
Versions
16
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

real-cancellable-promise - npm Package Compare versions

Comparing version 1.1.2 to 1.2.0

14

CHANGELOG.md

@@ -0,1 +1,7 @@

## 1.2.0
### Chores
- Update the signature of `CancellablePromise.race` to match that of `Promise.race` in the latest version of TypeScript. This should not be a breaking change for the vast majority of users. (#8)
## 1.1.2

@@ -5,4 +11,4 @@

- Make the `capture` function of `buildCancellablePromise` an identity function
from a type perspective.
- Make the `capture` function of `buildCancellablePromise` an identity function
from a type perspective.

@@ -13,3 +19,3 @@ ## 1.1.1

- Fix `CancellablePromise<T>` not being assignable to `Promise<T>`
- Fix `CancellablePromise<T>` not being assignable to `Promise<T>`

@@ -20,3 +26,3 @@ ## 1.1.0

- Publish ES module
- Publish ES module

@@ -23,0 +29,0 @@ ## 1.0.0

/**
* The most abstract thing we can cancel — a thenable with a cancel method.
*/
export declare type PromiseWithCancel<T> = PromiseLike<T> & {
export type PromiseWithCancel<T> = PromiseLike<T> & {
cancel(): void;

@@ -269,3 +269,3 @@ };

*/
static race<T>(values: readonly T[]): CancellablePromise<T extends PromiseLike<infer U> ? U : T>;
static race<T extends readonly unknown[] | []>(values: T): CancellablePromise<Awaited<T[number]>>;
/**

@@ -272,0 +272,0 @@ * @returns a `CancellablePromise` that resolves after `ms` milliseconds.

'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
/**

@@ -90,9 +88,10 @@ * If canceled, a [[`CancellablePromise`]] should throw an `Cancellation` object.

}
/* eslint-disable @typescript-eslint/no-explicit-any -- to match the types used for Promise in the official lib.d.ts */
/**
* Analogous to `Promise.catch`.
*/
catch(onRejected // eslint-disable-line @typescript-eslint/no-explicit-any -- to match the types used for Promise in the official lib.d.ts
) {
catch(onRejected) {
return this.then(undefined, onRejected);
}
/* eslint-enable @typescript-eslint/no-explicit-any */
/**

@@ -99,0 +98,0 @@ * Attaches a callback that is invoked when the Promise is settled

@@ -14,3 +14,3 @@ import { CancellablePromise } from './CancellablePromise';

*/
export declare type CaptureCancellablePromise = <P extends CancellablePromise<unknown>>(promise: P) => P;
export type CaptureCancellablePromise = <P extends CancellablePromise<unknown>>(promise: P) => P;
/**

@@ -17,0 +17,0 @@ * Used to build a single [[`CancellablePromise`]] from a multi-step asynchronous

{
"name": "real-cancellable-promise",
"version": "1.1.2",
"version": "1.2.0",
"description": "A simple cancellable promise implementation that cancels the underlying HTTP call.",

@@ -25,6 +25,5 @@ "keywords": [

"scripts": {
"build": "yarn clean && rollup -c .config/rollup.config.js",
"build": "yarn clean && rollup -c .config/rollup.config.mjs",
"clean": "rimraf dist",
"lint": "eslint",
"lint-all": "yarn lint .",
"lint": "eslint .",
"lint-staged": "lint-staged --no-stash",

@@ -45,27 +44,28 @@ "prepack": "yarn build",

"devDependencies": {
"@rollup/plugin-typescript": "^8.2.5",
"@swc/cli": "^0.1.51",
"@swc/core": "^1.2.92",
"@swc/jest": "^0.2.4",
"@types/jest": "^27.0.2",
"@typescript-eslint/eslint-plugin": "^4.32.0",
"@typescript-eslint/parser": "^4.32.0",
"eslint": "^7.32.0",
"eslint-config-airbnb-base": "^14.2.1",
"eslint-config-airbnb-typescript": "^14.0.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-import": "^2.24.2",
"eslint-plugin-jest": "^25.0.1",
"eslint-plugin-promise": "^5.1.0",
"husky": "^7.0.2",
"jest": "^27.2.4",
"lint-staged": "^11.1.2",
"prettier": "^2.4.1",
"rimraf": "^3.0.2",
"rollup": "^2.57.0",
"tslib": "^2.3.1",
"typedoc": "^0.22.4",
"typescript": "^4.4.3"
"@rollup/plugin-typescript": "^11.1.0",
"@swc/cli": "^0.1.62",
"@swc/core": "^1.3.50",
"@swc/jest": "^0.2.24",
"@types/jest": "^29.5.0",
"@typescript-eslint/eslint-plugin": "^5.58.0",
"@typescript-eslint/parser": "^5.58.0",
"eslint": "^8.38.0",
"eslint-config-airbnb-base": "^15.0.0",
"eslint-config-airbnb-typescript": "^17.0.0",
"eslint-config-prettier": "^8.8.0",
"eslint-plugin-import": "^2.27.5",
"eslint-plugin-jest": "^27.2.1",
"eslint-plugin-promise": "^6.1.1",
"husky": "^8.0.3",
"jest": "^29.5.0",
"lint-staged": "^13.2.1",
"prettier": "^2.8.7",
"prettier-plugin-packagejson": "^2.4.3",
"rimraf": "^5.0.0",
"rollup": "^3.20.2",
"tslib": "^2.5.0",
"typedoc": "^0.24.1",
"typescript": "^5.0.4"
},
"packageManager": "yarn@3.3.1"
"packageManager": "yarn@3.5.0"
}

@@ -7,12 +7,12 @@ # real-cancellable-promise

- ⚡ Compatible with [fetch](#fetch), [axios](#axios), and
[jQuery.ajax](#jQuery)
- 🐦 Lightweight — zero dependencies and less than 1 kB minified and gzipped
- 🏭 Used in production by [Interface
Technologies](http://www.iticentral.com/)
- 💻 Optimized for TypeScript
- ⚛ Built with React in mind
- 🔎 Compatible with
[react-query](https://react-query.tanstack.com/guides/query-cancellation)
query cancellation out of the box
- ⚡ Compatible with [fetch](#fetch), [axios](#axios), and
[jQuery.ajax](#jQuery)
- 🐦 Lightweight — zero dependencies and less than 1 kB minified and gzipped
- 🏭 Used in production by [Interface
Technologies](http://www.iticentral.com/)
- 💻 Optimized for TypeScript
- ⚛ Built with React in mind
- 🔎 Compatible with
[react-query](https://react-query.tanstack.com/guides/query-cancellation)
query cancellation out of the box

@@ -26,9 +26,9 @@ # The Basics

```ts
import { CancellablePromise } from 'real-cancellable-promise'
import { CancellablePromise } from 'real-cancellable-promise';
const cancellablePromise = new CancellablePromise(normalPromise, cancel)
const cancellablePromise = new CancellablePromise(normalPromise, cancel);
cancellablePromise.cancel()
cancellablePromise.cancel();
await cancellablePromise // throws a Cancellation object that subclasses Error
await cancellablePromise; // throws a Cancellation object that subclasses Error
```

@@ -44,3 +44,3 @@

```ts
new CancellablePromise(normalPromise, () => {})
new CancellablePromise(normalPromise, () => {});
```

@@ -56,20 +56,20 @@

export function cancellableFetch(
input: RequestInfo,
init: RequestInit = {}
input: RequestInfo,
init: RequestInit = {}
): CancellablePromise<Response> {
const controller = new AbortController()
const controller = new AbortController();
const promise = fetch(input, {
...init,
signal: controller.signal,
}).catch((e) => {
if (e.name === 'AbortError') {
throw new Cancellation()
}
const promise = fetch(input, {
...init,
signal: controller.signal,
}).catch((e) => {
if (e.name === 'AbortError') {
throw new Cancellation();
}
// rethrow the original error
throw e
})
// rethrow the original error
throw e;
});
return new CancellablePromise<Response>(promise, () => controller.abort())
return new CancellablePromise<Response>(promise, () => controller.abort());
}

@@ -79,4 +79,4 @@

const cancellablePromise = cancellableFetch(url, {
/* pass options here */
})
/* pass options here */
});
```

@@ -89,33 +89,33 @@

export function cancellableFetch<T>(
input: RequestInfo,
init: RequestInit = {}
input: RequestInfo,
init: RequestInit = {}
): CancellablePromise<T> {
const controller = new AbortController()
const controller = new AbortController();
const promise = fetch(input, {
...init,
signal: controller.signal,
const promise = fetch(input, {
...init,
signal: controller.signal,
})
.then((response) => {
// Handle the response object however you want
if (!response.ok) {
throw new Error(`Fetch failed with status code ${response.status}.`);
}
if (response.headers.get('content-type')?.includes('application/json')) {
return response.json();
} else {
return response.text();
}
})
.then((response) => {
// Handle the response object however you want
if (!response.ok) {
throw new Error(`Fetch failed with status code ${response.status}.`)
}
.catch((e) => {
if (e.name === 'AbortError') {
throw new Cancellation();
}
if (response.headers.get('content-type')?.includes('application/json')) {
return response.json()
} else {
return response.text()
}
})
.catch((e) => {
if (e.name === 'AbortError') {
throw new Cancellation()
}
// rethrow the original error
throw e;
});
// rethrow the original error
throw e
})
return new CancellablePromise<T>(promise, () => controller.abort())
return new CancellablePromise<T>(promise, () => controller.abort());
}

@@ -129,22 +129,24 @@ ```

```ts
export function cancellableAxios<T>(config: AxiosRequestConfig): CancellablePromise<T> {
const source = axios.CancelToken.source()
config = { ...config, cancelToken: source.token }
export function cancellableAxios<T>(
config: AxiosRequestConfig
): CancellablePromise<T> {
const source = axios.CancelToken.source();
config = { ...config, cancelToken: source.token };
const promise = axios(config)
.then((response) => response.data)
.catch((e) => {
if (e instanceof axios.Cancel) {
throw new Cancellation()
}
const promise = axios(config)
.then((response) => response.data)
.catch((e) => {
if (e instanceof axios.Cancel) {
throw new Cancellation();
}
// rethrow the original error
throw e
})
// rethrow the original error
throw e;
});
return new CancellablePromise<T>(promise, () => source.cancel())
return new CancellablePromise<T>(promise, () => source.cancel());
}
// Use just like normal axios:
const cancellablePromise = cancellableAxios({ url })
const cancellablePromise = cancellableAxios({ url });
```

@@ -156,18 +158,18 @@

export function cancellableJQueryAjax<T>(
settings: JQuery.AjaxSettings
settings: JQuery.AjaxSettings
): CancellablePromise<T> {
const xhr = $.ajax(settings)
const xhr = $.ajax(settings);
const promise = xhr.catch((e) => {
if (e.statusText === 'abort') throw new Cancellation()
const promise = xhr.catch((e) => {
if (e.statusText === 'abort') throw new Cancellation();
// rethrow the original error
throw e
})
// rethrow the original error
throw e;
});
return new CancellablePromise<T>(promise, () => xhr.abort())
return new CancellablePromise<T>(promise, () => xhr.abort());
}
// Use just like normal $.ajax:
const cancellablePromise = cancellableJQueryAjax({ url, dataType: 'json' })
const cancellablePromise = cancellableJQueryAjax({ url, dataType: 'json' });
```

@@ -186,35 +188,35 @@

## React: Prevent setState after unmount
## React: Cancel the API call when the component unmounts
React will give you a warning if you attempt to update a component's state after
it has unmounted. This will happen if your component makes an API call but gets
unmounted before the API call completes.
If your React component makes an API call, you probably don't care about the result of that API call after the component has unmounted. You can cancel the API in the cleanup function of an effect like this:
You can fix this by canceling the API call in the cleanup function of an effect.
```tsx
function listBlogPosts(): CancellablePromise<Post[]> {
// call the API
// call the API
}
export function Blog() {
const [posts, setPosts] = useState<Post[]>([])
const [posts, setPosts] = useState<Post[]>([]);
useEffect(() => {
const cancellablePromise = listBlogPosts().then(setPosts).catch(console.error)
useEffect(() => {
const cancellablePromise = listBlogPosts()
.then(setPosts)
.catch(console.error);
// The promise will get canceled when the component unmounts
return cancellablePromise.cancel
}, [])
// The promise will get canceled when the component unmounts
return cancellablePromise.cancel;
}, []);
return (
<div>
{posts.map((p) => {
/* ... */
})}
</div>
)
return (
<div>
{posts.map((p) => {
/* ... */
})}
</div>
);
}
```
Before React 18, this was necessary to prevent the infamous "setState after unmount" warning. This warning was removed from React in React 18 because setting state after the component unmounts is usually not indicative of a real problem.
[CodeSandbox: prevent setState after

@@ -225,33 +227,33 @@ unmount](https://codesandbox.io/s/real-cancellable-promise-prevent-setstate-after-unmount-2zqb0?file=/src/App.tsx)

Sometimes API calls have parameters, like a search string entered by the user.
Sometimes API calls have parameters, like a search string entered by the user. If the query parameters change, you should cancel any in-progress API calls.
```tsx
function searchUsers(searchTerm: string): CancellablePromise<User[]> {
// call the API
// call the API
}
export function UserList() {
const [searchTerm, setSearchTerm] = useState('')
const [users, setUsers] = useState<User[]>([])
const [searchTerm, setSearchTerm] = useState('');
const [users, setUsers] = useState<User[]>([]);
// In a real app you should debounce the searchTerm
useEffect(() => {
const cancellablePromise = searchUsers(searchTerm)
.then(setUsers)
.catch(console.error)
// In a real app you should debounce the searchTerm
useEffect(() => {
const cancellablePromise = searchUsers(searchTerm)
.then(setUsers)
.catch(console.error);
// The old API call gets canceled whenever searchTerm changes. This prevents
// setUsers from being called with incorrect results if the API calls complete
// out of order.
return cancellablePromise.cancel
}, [searchTerm])
// The old API call gets canceled whenever searchTerm changes. This prevents
// setUsers from being called with incorrect results if the API calls complete
// out of order.
return cancellablePromise.cancel;
}, [searchTerm]);
return (
<div>
<SearchInput searchTerm={searchTerm} onChange={setSearchTerm} />
{users.map((u) => {
/* ... */
})}
</div>
)
return (
<div>
<SearchInput searchTerm={searchTerm} onChange={setSearchTerm} />
{users.map((u) => {
/* ... */
})}
</div>
);
}

@@ -272,15 +274,15 @@ ```

function bigQuery(userId: number): CancellablePromise<QueryResult> {
return buildCancellablePromise(async (capture) => {
const userPromise = api.user.get(userId)
const rolePromise = api.user.listRoles(userId)
return buildCancellablePromise(async (capture) => {
const userPromise = api.user.get(userId);
const rolePromise = api.user.listRoles(userId);
const [user, roles] = await capture(
CancellablePromise.all([userPromise, rolePromise])
)
const [user, roles] = await capture(
CancellablePromise.all([userPromise, rolePromise])
);
// User must be loaded before this query can run
const customer = await capture(api.customer.get(user.customerId))
// User must be loaded before this query can run
const customer = await capture(api.customer.get(user.customerId));
return { user, roles, customer }
})
return { user, roles, customer };
});
}

@@ -303,11 +305,11 @@ ```

try {
await capture(cancellablePromise)
await capture(cancellablePromise);
} catch (e) {
if (e instanceof Cancellation) {
// do nothing — the component probably just unmounted.
// or you could do something here it's up to you 😆
return
}
if (e instanceof Cancellation) {
// do nothing — the component probably just unmounted.
// or you could do something here it's up to you 😆
return;
}
// log the error or display it to the user
// log the error or display it to the user
}

@@ -322,8 +324,8 @@ ```

```ts
const cancellablePromise = pseudoCancellable(normalPromise)
const cancellablePromise = pseudoCancellable(normalPromise);
// Later...
cancellablePromise.cancel()
cancellablePromise.cancel();
await cancellablePromise // throws Cancellation object if promise did not already resolve
await cancellablePromise; // throws Cancellation object if promise did not already resolve
```

@@ -334,3 +336,3 @@

```ts
await CancellablePromise.delay(1000) // wait 1 second
await CancellablePromise.delay(1000); // wait 1 second
```

@@ -337,0 +339,0 @@

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc