Cancelable promise ecosystem based on native Promise
: coroutines, async iterators, decorators, utilities, third-party library helpers.
- cancelable promise implementation built on top of ES Promise
- generator-based cancelable replacements for
async..await
and async iterators - lazily evaluated cancelable promises
- cancelable Fetch API
- utility toolbox (
delay
,timeout
, etc) - library helpers (Axios, Bluebird, RxJS, etc)
- decorators for TypeScript and Babel
- base packages to be used with custom promise implementation
- UMD and ESM builds for modern and legacy browsers and Node.js
- TypeScript-ready
Cancellation-aware promise utilities:
Name | Version | Description |
---|---|---|
@cancjs/promise |
|
Cancelable promise implementation based on ES Promise |
@cancjs/coroutine |
|
Cancelable generator-based drop-in replacements for async..await and async iterators
|
@cancjs/fetch |
|
Cross-platform Fetch API that uses cancelable promises |
@cancjs/lazy-promise |
|
Cancelable lazily evaluated promise-like class |
@cancjs/toolbox |
|
A collection of cancellation-aware promise helper functions and ponyfills |
General-purpose promise utilities that use built-in Promise
as promise implementation where applicable:
Package | Version | Description |
---|---|---|
@cancjs/coroutine-native |
|
Generator-based drop-in replacements for async..await and async iterators
|
@cancjs/lazy-promise-native |
|
Lazily evaluated promise-like class |
@cancjs/toolbox |
|
A collection of promise helper functions |
Cancellation is a special form of promise rejection with cancel error that triggers registered handlers for the entire cancellation-aware promise chain.
canc
promises implement two-way cancellation mechanism that treats promise chains as subscriptions:
-
cancellation propagates down the promise chain when parent promise is canceled
-
cancellation bubbles up the chain when all child promises are canceled and parent promise value is no longer consumed
Cancellation bubbling can be explicitly disabled on parent promise in case a promise causes side effects that shouldn't be implicitly discarded.
Two-way cancellation mechanism is supported for all common ways to establish a promise chain, including all
, race
, etc composition methods and coroutine yield
.
A chain is cancelable only if it consists of canc
promises. This requires to use cancellation-aware wrappers for Fetch API and third-party librararies, async
and async*
need to be replaced with cancelable generator-based coroutines.
Promise cancellation is highly beneficial in real life scenarios yet it's not a part of existing ECMAScript specification. JavaScript API like Fetch AbortController
use their own mechanisms that aren't unified with native promises.
A situation that is common in modern JavaScript applications is that a process like network request that stands behind long-running asynchronous task is abortable, consumers need to unsubscribe from results and abort initial process when it's no longer needed. This eventually becomes harder with uncancelable promises when a task is composed of smaller independent tasks.
See examples for more use cases.
-
No official solution. Native cancelable promises were incompatible with ES6 promise semantics, provided one-way cancellation, used unwieldy cancel tokens and have been abandoned.
-
Bluebird stepped aside. Bluebird has bulky stable API and has been largely superseded by ES promises where applicable, particularly due to
async..await
. Two-way cancellation is disabled by default and incompatible with native promise semantics. -
No universal third-party options. JavaScript community provides no comprehensive alternatives based on native promises. Renowned
p-*
package collection only supports one-way cancellation and targets Node.js. -
Observables aren't a magic bullet. Observables can provide a superset of promise features, as well as cancellation. However, observables don't offer expressive sugar similar to
async..await
, cancellation may be lost in promise interop. Observables are push-based and cannot displace pull-basedasync*
async iterators. RxJS is commonly used implementation with complex API, no native observable implementation exists yet.
Packages rely on following ECMAScript 2015+ features: Symbol
(ES2018 for async iterators), Reflect
, Promise
(ES2018 for finally
, ES2020 for allSettled
), Object.assign
, Object.setPrototypeOf
.
Supported in modern browsers and Node.js:
- Chrome 49
- Opera 36
- Edge 12
- Firefox 42
- Safari macOS/iOS 10
- Android 7 (WebView)
- Node.js 6
Supported in legacy browsers and Node.js with core-js
or polyfill.io
:
- Chrome 5
- Opera 12
- Edge 12
- IE 11
- Firefox 4
- Safari macOS/iOS 5
- Android 4.4 (WebView, browser)
- Node.js 0.10
The incompatibility between native and polyfilled Promise
and Reflect
in engines with incomplete ES6 support (Node.js 0.12 to 5, etc) requires to match globals before polyfilling:
Modular environment
var _global = typeof globalThis !== 'undefined' && globalThis
|| typeof self !== 'undefined' && self
|| typeof global !== 'undefined' && global;
if (!('Reflect' in _global) && 'Promise' in _global)
delete _global.Promise;
require('core-js/stable');
Browser webpage
<script>
if (!('Reflect' in window) && 'Promise' in window)
delete window.Promise;
</script>
<!-- no es2020 allSettled yet -->
<script src="https://polyfill.io/v3/polyfill.min.js?features=es2015,es2018&flags=always,gated"></script>
Can be found in examples section.
You are welcome to participate through issues and pull requests!