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.0.0 to 1.1.0-alpha.0

.husky/pre-commit

9

CONTRIBUTING.md

@@ -12,2 +12,11 @@ # Contributing to real-cancellable-promise

## Publishing
1. Make sure that the CI workflow succeeded.
2. Increment version in `package.json`.
3. (If production release) Add a git tag in the format `v1.0.0`.
4. Commit and push. Remember to push tags as well with `git push --tags`.
5. `yarn npm publish` or `yarn npm publish --tag next`. The `prepublish` script will automatically do a clean and build.
6. (If production release) Create a new release in GitHub.
## TypeDoc

@@ -14,0 +23,0 @@

276

dist/index.js

@@ -1,15 +0,261 @@

"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __exportStar = (this && this.__exportStar) || function(m, exports) {
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
Object.defineProperty(exports, "__esModule", { value: true });
__exportStar(require("./Cancellation"), exports);
__exportStar(require("./CancellablePromise"), exports);
__exportStar(require("./utils"), exports);
/**
* If canceled, a [[`CancellablePromise`]] should throw an `Cancellation` object.
*/
class Cancellation extends Error {
constructor(message = 'Promise canceled.') {
super(message);
}
}
/** @internal */
const noop = () => { };
/**
* Determines if an arbitrary value is a thenable with a cancel method.
*/
function isPromiseWithCancel(value) {
return (typeof value === 'object' &&
typeof value.then === 'function' &&
typeof value.cancel === 'function');
}
/**
* A promise with a `cancel` method.
*
* If canceled, the `CancellablePromise` will reject with a [[`Cancellation`]]
* object.
*
* @typeParam T what the `CancellablePromise` resolves to
*/
class CancellablePromise {
/**
* @param promise a normal promise or thenable
* @param cancel a function that cancels `promise`. **Calling `cancel` after
* `promise` has resolved must be a no-op.**
*/
constructor(promise, cancel) {
this.promise = Promise.resolve(promise);
this.cancel = cancel;
}
/**
* Analogous to `Promise.then`.
*
* `onFulfilled` on `onRejected` can return a value, a normal promise, or a
* `CancellablePromise`. So you can make a chain a `CancellablePromise`s
* like this:
*
* ```
* const overallPromise = cancellableAsyncFunction1()
* .then(cancellableAsyncFunction2)
* .then(cancellableAsyncFunction3)
* .then(cancellableAsyncFunction4)
* ```
*
* Then if you call `overallPromise.cancel`, `cancel` is called on all
* `CancellablePromise`s in the chain! In practice, this means that
* whichever async operation is in progress will be canceled.
*
* @returns a new CancellablePromise
*/
then(onFulfilled, onRejected) {
let fulfill;
let reject;
let callbackPromiseWithCancel;
if (onFulfilled) {
fulfill = (value) => {
const nextValue = onFulfilled(value);
if (isPromiseWithCancel(nextValue))
callbackPromiseWithCancel = nextValue;
return nextValue;
};
}
if (onRejected) {
reject = (reason) => {
const nextValue = onRejected(reason);
if (isPromiseWithCancel(nextValue))
callbackPromiseWithCancel = nextValue;
return nextValue;
};
}
const newPromise = this.promise.then(fulfill, reject);
const newCancel = () => {
this.cancel();
callbackPromiseWithCancel === null || callbackPromiseWithCancel === void 0 ? void 0 : callbackPromiseWithCancel.cancel();
};
return new CancellablePromise(newPromise, newCancel);
}
/**
* Analogous to `Promise.catch`.
*/
catch(onRejected) {
return this.then(undefined, onRejected);
}
/**
* Attaches a callback that is invoked when the Promise is settled
* (fulfilled or rejected). The resolved value cannot be modified from the
* callback.
* @param onFinally The callback to execute when the Promise is settled
* (fulfilled or rejected).
* @returns A Promise for the completion of the callback.
*/
finally(onFinally) {
return new CancellablePromise(this.promise.finally(onFinally), this.cancel);
}
static resolve(value) {
return new CancellablePromise(Promise.resolve(value), noop);
}
/**
* Analogous to `Promise.reject`.
*
* Like `CancellablePromise.resolve`, canceling the returned
* `CancellablePromise` is a no-op.
*
* @param reason this should probably be an `Error` object
*/
static reject(reason) {
return new CancellablePromise(Promise.reject(reason), noop);
}
/**
* Analogous to `Promise.all`.
*
* @param values an array that may contain `CancellablePromise`s, promises,
* thenables, and resolved values
* @returns a [[`CancellablePromise`]], which, if canceled, will cancel each
* of the promises passed in to `CancellablePromise.all`.
*/
static all(values) {
return new CancellablePromise(Promise.all(values), () => {
for (const value of values) {
if (isPromiseWithCancel(value))
value.cancel();
}
});
}
static allSettled(values) {
const cancel = () => {
for (const value of values) {
if (isPromiseWithCancel(value)) {
value.cancel();
}
}
};
return new CancellablePromise(Promise.allSettled(values), cancel);
}
/**
* Creates a `CancellablePromise` that is resolved or rejected when any of
* the provided `Promises` are resolved or rejected.
* @param values An array of `Promises`.
* @returns A new `CancellablePromise`. Canceling it cancels all of the input
* promises.
*/
static race(values) {
const cancel = () => {
for (const value of values) {
if (isPromiseWithCancel(value)) {
value.cancel();
}
}
};
return new CancellablePromise(Promise.race(values), cancel);
}
// Promise.any is an ES2021 feature. Not yet implemented.
// /**
// * The any function returns a `CancellablePromise` that is fulfilled by the
// * first given promise to be fulfilled, or rejected with an `AggregateError`
// * containing an array of rejection reasons if all of the given promises are
// * rejected. It resolves all elements of the passed iterable to promises as
// * it runs this algorithm.
// * @param values An array or iterable of Promises.
// * @returns A new `CancellablePromise`.
// */
// any<T>(values: (T | PromiseLike<T>)[] | Iterable<T | PromiseLike<T>>): CancellablePromise<T> {
// return new CancellablePromise(Promise.any(values), cancel))
// }
/**
* @returns a `CancellablePromise` that resolves after `ms` milliseconds.
*/
static delay(ms) {
let timer;
let rejectFn = noop;
const promise = new Promise((resolve, reject) => {
timer = setTimeout(() => {
resolve();
rejectFn = noop;
}, ms);
rejectFn = reject;
});
return new CancellablePromise(promise, () => {
if (timer)
clearTimeout(timer);
rejectFn(new Cancellation());
});
}
}
/**
* Takes in a regular `Promise` and returns a `CancellablePromise`. If canceled,
* the `CancellablePromise` will immediately reject with a `Cancellation`, but the asynchronous
* operation will not truly be aborted.
*
* Analogous to
* [make-cancellable-promise](https://www.npmjs.com/package/make-cancellable-promise).
*/
function pseudoCancellable(promise) {
let canceled = false;
let rejectFn = noop;
const newPromise = new Promise((resolve, reject) => {
rejectFn = reject;
// eslint-disable-next-line promise/catch-or-return -- no catch method on PromiseLike
promise.then((result) => {
if (!canceled) {
resolve(result);
rejectFn = noop;
}
return undefined;
}, (e) => {
if (!canceled)
reject(e);
});
});
function cancel() {
canceled = true;
rejectFn(new Cancellation());
}
return new CancellablePromise(newPromise, cancel);
}
/**
* Used to build a single [[`CancellablePromise`]] from a multi-step asynchronous
* flow.
*
* When the overall promise is canceled, each captured promise is canceled. In practice,
* this means the active asynchronous operation is canceled.
*
* ```
* function query(id: number): CancellablePromise<QueryResult> {
* return buildCancellablePromise(async capture => {
* const result1 = await capture(api.method1(id))
*
* // do some stuff
*
* const result2 = await capture(api.method2(result1.id))
*
* return { result1, result2 }
* })
* }
* ```
*
* @param innerFunc an async function that takes in a `capture` function and returns
* a regular `Promise`
*/
function buildCancellablePromise(innerFunc) {
const capturedPromises = [];
const capture = (promise) => {
capturedPromises.push(promise);
return promise;
};
function cancel() {
capturedPromises.forEach((p) => p.cancel());
}
return new CancellablePromise(innerFunc(capture), cancel);
}
export { CancellablePromise, Cancellation, buildCancellablePromise, isPromiseWithCancel, pseudoCancellable };
{
"name": "real-cancellable-promise",
"version": "1.0.0",
"version": "1.1.0-alpha.0",
"description": "A simple cancellable promise implementation that cancels the underlying HTTP call.",

@@ -18,7 +18,8 @@ "keywords": [

"license": "MIT",
"main": "dist/index.js",
"main": "dist/index.cjs",
"module": "dist/index.js",
"types": "dist/index.d.ts",
"scripts": {
"clean": "rimraf dist",
"build": "yarn clean && tsc",
"build": "yarn clean && rollup -c",
"test": "jest",

@@ -28,4 +29,14 @@ "lint": "eslint",

"prettier-all": "prettier '**/*.ts?(x)'",
"prepublish": "yarn build"
"prepublish": "yarn build && pinst --disable",
"postpublish": "pinst --enable",
"postinstall": "husky install",
"lint-staged": "lint-staged --no-stash"
},
"lint-staged": {
"*.ts?(x)": [
"eslint --max-warnings 0 --fix",
"prettier --write"
],
"*.{md,js,cjs,yml,json}": "prettier --write"
},
"devDependencies": {

@@ -35,2 +46,3 @@ "@babel/core": "^7.15.5",

"@babel/preset-typescript": "^7.15.0",
"@rollup/plugin-typescript": "^8.2.5",
"@types/jest": "^27.0.1",

@@ -46,5 +58,10 @@ "@typescript-eslint/eslint-plugin": "^4.31.1",

"eslint-plugin-promise": "^5.1.0",
"husky": "^7.0.2",
"jest": "^27.2.0",
"lint-staged": "^11.1.2",
"pinst": "^2.1.6",
"prettier": "^2.4.0",
"rimraf": "^3.0.2",
"rollup": "^2.57.0",
"tslib": "^2.3.1",
"typedoc": "^0.22.3",

@@ -51,0 +68,0 @@ "typescript": "^4.4.3"

71

README.md
# real-cancellable-promise
A simple cancellable promise implementation for JavaScript and TypeScript.
Unlike [p-cancelable](https://www.npmjs.com/package/p-cancelable) and
[make-cancellable-promise](https://www.npmjs.com/package/make-cancellable-promise)
which only prevent your promise's callbacks from executing,
**`real-cancellable-promise` cancels the underlying asynchronous operation
(usually an HTTP call).** That's why it's called **Real** Cancellable Promise.
[Read the announcement post for a full explanation.](https://dev.to/srmagura/announcing-real-cancellable-promise-gkd) In particular, see the "Prior art" section for a comparison to existing cancellable promise libraries.
- ⚛ Built with React in mind — no more "setState after unmount" warnings!
- ⚡ Compatible with [fetch](#fetch), [axios](#axios), and
[jQuery.ajax](#jQuery)
- ⚛ Built with React in mind — no more "setState after unmount" errors!
- 🐦 Lightweight — zero dependencies and less than 1 kB minified and gzipped

@@ -37,2 +34,13 @@ - 🏭 Used in production by [Interface

### Important
The `CancellablePromise` constructor takes in a `promise` and a `cancel` function.
Your `cancel` function **MUST** cause `promise` to reject with a `Cancellation` object.
This will **NOT** work, your callbacks with still run:
```ts
new CancellablePromise(normalPromise, () => {})
```
# Usage with HTTP Libraries

@@ -45,2 +53,33 @@

```ts
export function cancellableFetch(
input: RequestInfo,
init: RequestInit = {}
): CancellablePromise<Response> {
const controller = new AbortController()
const promise = fetch(input, {
...init,
signal: controller.signal,
}).catch((e) => {
if (e.name === 'AbortError') {
throw new Cancellation()
}
// rethrow the original error
throw e
})
return new CancellablePromise<Response>(promise, () => controller.abort())
}
// Use just like normal fetch:
const cancellablePromise = cancellableFetch(url, {
/* pass options here */
})
```
<details>
<summary><code>fetch</code> with response handling</summary>
```ts
export function cancellableFetch<T>(

@@ -79,9 +118,6 @@ input: RequestInfo,

}
// Use just like normal fetch:
const cancellablePromise = cancellableFetch(url, {
/* pass options here */
})
```
</details>
## <a name="axios" href="https://axios-http.com/">axios</a>

@@ -137,7 +173,7 @@

# [API Reference](https://srmagura.github.io/real-cancellable-promise)
# [API Reference](https://srmagura.github.io/real-cancellable-promise/modules.html)
`CancellablePromise` supports all the methods that the normal `Promise` object
supports, except `Promise.any` (ES2021). See the [API
Reference](https://srmagura.github.io/real-cancellable-promise) for details.
Reference](https://srmagura.github.io/real-cancellable-promise/modules.html) for details.

@@ -275,4 +311,3 @@ # Examples

Sometimes you need to call an asynchronous function that doesn't support
cancellation. In this case, you can use `pseudoCancellable` to prevent the
promise from resolving after `cancel` has been called.
cancellation. In this case, you can use `pseudoCancellable`:

@@ -288,8 +323,2 @@ ```ts

This will **NOT** work, your callbacks with still run:
```ts
const cancellablePromise = new CancellablePromise(normalPromise, () => {})
```
## `CancellablePromise.delay`

@@ -296,0 +325,0 @@

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