Package Exports
- c-promise2
This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (c-promise2) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.
Readme
Table of contents
- SYNOPSIS
- Why
- Features
- Installation
- Examples:
- Signals handling
- Using generators
- Related projects
- API Reference
- License
SYNOPSIS ✨
CPromise library provides an advanced version of the built-in Promise by subclassing. You might be interested in using it if you need:
- cancel the promise (including nested)
- capture progress
- pause the promise
- pending timeout
- concurrent limit for
all
andallSettled
methods withmapper
reducer - advanced signal communication
In terms of the library the cancellation means rejection with a special error subclass.
const promise= new CPromise((resolve, reject, {onCancel, onPause, onResume})=>{
onCancel(()=>{
//optionally some code here to abort your long-term task (abort request, stop timers etc.)
});
}).then(
value => console.log(`Done: ${value}`),
(err, scope) => {
console.warn(`Failed: ${err}`); // Failed: CanceledError: canceled
console.log('chain isCanceled:', promise.isCanceled); // true
console.log('promise isCanceled:', scope.isCanceled); // true
}
);
console.log('isPromise:', promise instanceof Promise); // true
setTimeout(()=> promise.cancel(), 1000);
Why ❓
You may run into a problem when you need to cancel some long-term asynchronous operation before it will be completed with success or failure, just because the result has lost its relevance to you.
Features
- no dependencies (except [native] Promise)
- built-in
AbortController
class - browser support
- supports two ways to make your promise internal code cancellable:
onCancel
callbacks (clear timers, abort requests)signal
provided by the AbortController (to wrap API like fetch method)
- 🔥 supports cancellation of the whole chain
- 🔥 supports generator to CPromise resolving (something similar like co library does);
- 🔥 progress capturing support
CPromise.all
supports concurrency limitCPromise.all
andCPromise.race
methods have cancellation support, so the others nested pending promises will be canceled when the resulting promise settled- promise suspending (using
pause
andresume
methods) - custom signals (
emitSignal
) delay
method to return a promise that will be resolved with the value after a timeout- ability to set the
weight
for each promise in the chain to manage the impact on chain progress - ability to attach meta info on each set of the progress
catch
method supports error class filtering- Supports listening to multiple
AbortController
signals
Installation 🔨
- Install for node.js using npm/yarn:
$ npm install c-promise2
$ yarn add c-promise2
The package consists pre-built bundles for umd, cjs, mjs versions which can be found in the ./dist/
directory
- Import the library:
import CPromise from "c-promise2";
// const CPromise = require("c-promise2"); // using require
// import CPromise from "c-promise2/dev"; // development version
const chain= CPromise.delay(1000, 'It works!').then(message => console.log('Done', message));
//chain.cancel();
As an alternative you can use any CDN with npm support:
development UMD version with (additional error handling activated)
production UMD version (or minified ~9KB)
Examples
Progress capturing and cancellation
Basic example (Live demo):
import CPromise from 'c-promise';
const delay= (ms, value)=>{
return new CPromise((resolve, reject, {onCancel}) => {
const timer = setTimeout(resolve, ms, value);
onCancel(() => {
clearTimeout(timer);
console.log('clear timeout');
}) // clear internal operations on 'cancel' event
})
}
const promise = CPromise.all([
delay(1000, 'a'),
delay(2000, 'b'),
delay(3000, 'c'),
delay(4000, 'd'),
delay(5000, 'e'),
])
.progress(value=> console.log(`Progress: ${(value * 100).toFixed(1)}%`));
console.log('isPromise:', promise instanceof Promise); // true
(async()=>{
try {
console.log(`Done: `, await promise);
}catch(err){
console.warn(`Failed: ${err}`);
console.log('isCanceled:', promise.isCanceled);
}
})()
setTimeout(()=> promise.cancel(), 3100); // cancel the promise after 3100ms
Console output:
isPromise: true
Progress: 20.0%
Progress: 40.0%
Progress: 60.0%
clear timeout
clear timeout
isCanceled: true
Failed: CanceledError: canceled
Process finished with exit code 0
Pause / resume promises
See the live demo
import CPromise from 'c-promise';
function cancelableDelay(ms, value){
return new CPromise(function(resolve, reject, {onCancel, onPause, onResume}){
let timestamp= Date.now();
let timeLeft;
let timer= setTimeout(resolve, ms, value);
onPause(()=>{
console.log(`Pause`);
clearTimeout(timer);
timer=0;
timeLeft= ms - (Date.now()- timestamp);
timestamp= Date.now();
});
onResume(()=>{
console.log(`Resume`);
timer= setTimeout(resolve, timeLeft, value);
});
onCancel(()=>{
console.log(`Cancel`);
timer && clearTimeout(timer);
})
});
}
const chain= cancelableDelay(1000, 123)
.then(
value=> console.log(`Done:`, value),
err=> console.warn(`Fail: ${err}`)
);
setTimeout(()=>{
chain.pause();
setTimeout(()=>{
chain.resume();
}, 5000);
}, 100);
Abortable fetch with timeout
This is how an abortable fetch (live example) with a timeout might look like
function fetchWithTimeout(url, {timeout, ...fetchOptions}= {}) {
return new CPromise((resolve, reject, {signal}) => {
fetch(url, {...fetchOptions, signal}).then(resolve, reject)
}, timeout)
}
const promise= fetchWithTimeout('http://localhost/', {timeout: 5000})
.then(response => response.json())
.then(data => console.log(`Done: `, data), err => console.log(`Error: `, err))
setTimeout(()=> promise.cancel(), 1000);
// you able to call cancel() at any time to cancel the entire chain at any stage
// the related network request will also be aborted
You can use the cp-fetch package which provides a CPromise wrapper for fetch API.
Wrapping axios request
function cancelableAxios(url){
return new CPromise((resolve, reject, {onCancel})=>{
axios.get(url, {
cancelToken: new axios.CancelToken(function executor(cancel) {
onCancel(cancel)
})
}).then(resolve, reject);
});
}
You can use the cp-axios package to get Axios to work with CPromise.
Concurrency limitation:
import CPromise from "c-promise2";
import cpFetch from "cp-fetch";
(async()=>{
await CPromise.all([
'url1',
'url2',
'url3',
'url4',
'url5',
'url6',
], {
mapper: async (url) => {
return cpFetch(url);
},
concurrency: 2
})
console.log('Done');
});
// Or
(async()=>{
await CPromise.all(function*(){
const urls= [
'url1',
'url2',
'url3',
'url4',
'url5',
'url6',
];
for(let url of urls){
yield cpFetch('url1');
}
}, {
concurrency: 2
})
console.log('Done');
})();
Using with React
Check out this live demo
import CPromise from "c-promise2";
import cFetch from "cp-fetch";
export class AsyncComponent extends React.Component {
state = {};
async componentDidMount() {
console.log("mounted");
this.controller = new CPromise.AbortController();
try {
const json = await this.myAsyncTask(
"https://run.mocky.io/v3/7b038025-fc5f-4564-90eb-4373f0721822"
);
console.log("json:", json);
await this.myAsyncTaskWithDelay(1000, 123); // just another async task
this.setState({ text: JSON.stringify(json) });
} catch (err) {
if (CPromise.isCanceledError(err)) {
console.log("tasks terminated");
}
}
}
myAsyncTask(url) {
return CPromise.from(function* () {
const response = yield cFetch(url); // cancellable request
// some another promises here
return yield response.json();
}).listen(this.controller.signal);
}
// another one cancellable async task
myAsyncTaskWithDelay(ms, value) {
return new CPromise((resolve, reject, { onCancel }) => {
const timer = setTimeout(resolve, ms, value);
onCancel(() => {
console.log("timeout cleared");
clearTimeout(timer);
});
}).listen(this.controller.signal);
}
render() {
return (
<div>
AsyncComponent: <span>{this.state.text}</span>
</div>
);
}
componentWillUnmount() {
console.log("unmounted");
this.controller.abort(); // kill all tasks
}
}
Using hooks and CPromise cancel
method Live Demo:
import React, { useEffect, useState } from "react";
import CPromise from "c-promise2";
import cpFetch from "cp-fetch";
function MyComponent(props) {
const [text, setText] = useState("fetching...");
useEffect(() => {
console.log("mount");
const promise = cpFetch(props.url)
.then((response) => response.json())
.then((value) => CPromise.delay(1000, value))
.then((json) => setText(`Success: ${JSON.stringify(json)}`))
.canceled() // catch CanceledError
.catch((err) => {
setText(`Failed: ${err}`);
});
return () => {
console.log("unmount");
promise.cancel();
};
}, [props.url]);
return <p>{text}</p>;
}
Signals handling
Every CPromise instance could handle "signals", emitted using emitSignal
method.
The method emits a signal
event on each pending promise in the chain until some handler returns true
as the result.
This method is used internally for predefined system signals for cancellation and suspending actions.
const CPromise= require('../lib/c-promise');
const chain= new CPromise((resolve, reject, scope)=>{
scope.on('signal', (type, data) => {
if (type === 'inc') { // ignore other signal types
console.log(`Signal ${type} handled`);
resolve(data.x + 1);
return true; // we have accepted this signal, so we should return `true` to stop the propagation
}
});
}).then(
(value)=> console.log(`Done: ${value}`),
(err)=> console.log(`Failed: ${err}`)
)
setTimeout(() => {
// returns true
console.log(`Inc signal result: ${chain.emitSignal('inc', {x: 2})}`);
// returns false because there are no handlers to catch this signal type
console.log(`Custom signal result: ${chain.emitSignal('custom')}`);
});
Console output:
Signal inc handled
Inc signal result: true
Custom signal result: false
Done: 3
Process finished with exit code 0
Using Generators as an alternative of ECMA async functions
Generally, you able to use CPromise with ES6 async functions,
but if you need some specific functionality such as progress capturing or cancellation,
you need to use generators instead of async functions to make it work.
This is because the async function leads all the nested thenables into its own Promise class,
and there is nothing we can do about it. Generators allow you to write asynchronous code
just in the same way as async functions do, just use yield
instead of await
.
See the live demo
import CPromise from "c-promise2";
const promise= CPromise.from(function*(){
this.innerWeight(12); //optionally set the expected internal progress score of the nested chain
yield CPromise.delay(1000);
yield [CPromise.delay(1000), CPromise.delay(1500)] // resolve chains using CPromise.all([...chains]);
yield [[CPromise.delay(1000), CPromise.delay(1500)]] // resolve chains using CPromise.race([...chains]);
yield new CPromise(resolve=> resolve(true)); // any thenable object will be resolved
return "It works!";
})
.progress(value=> console.log(`Progress: ${value}`))
.then(message=> console.log(`Done: ${message}`));
Related projects
- cp-axios - a simple axios wrapper that provides an advanced cancellation api
- cp-fetch - fetch with timeouts and request cancellation
API Reference
Cancellable Promise with extra features
CPromise~CPromise ⇐ Promise
CPromise class
Kind: inner class of CPromise
Extends: Promise
- ~CPromise ⇐
Promise
- new CPromise([executor], [options])
- instance
- .signal :
AbortSignal
- .isPending ⇒
Boolean
- .isCanceled ⇒
Boolean
- .isCaptured ⇒
Boolean
- .isPaused ⇒
Boolean
- .parent ⇒
CPromise
|null
- .totalWeight([weight]) ⇒
Number
|CPromise
- .innerWeight([weight]) ⇒
Number
|CPromise
- .progress([value], [data]) ⇒
Number
|CPromise
- .propagate(type, data) ⇒
CPromise
- .captureProgress([options]) ⇒
CPromise
- .scopes() ⇒
Array.<CPromise>
- .timeout([ms]) ⇒
Number
|CPromise
- .weight([weight]) ⇒
Number
|CPromise
- .label([label]) ⇒
Number
|CPromise
- .resolve(value) ⇒
CPromise
- .reject(err) ⇒
CPromise
- .pause() ⇒
Boolean
- .resume() ⇒
Boolean
- .cancel([reason])
- .emitSignal([data], type, [handler]) ⇒
Boolean
- .delay(ms) ⇒
CPromise
- .then(onFulfilled, [onRejected]) ⇒
CPromise
- .catch(onRejected, [filter]) ⇒
CPromise
- .canceled([onCanceled]) ⇒
CPromise
- .listen(signal) ⇒
CPromise
- .on(type, listener, [prepend]) ⇒
CPromise
- .off(type, listener) ⇒
CPromise
- .listenersCount(type) ⇒
Number
- .hasListeners(type) ⇒
Boolean
- .once(type, listener) ⇒
CPromise
- .emit(type, ...args) ⇒
CPromise
- .emitHook(type, ...args) ⇒
Boolean
- .signal :
- static
- .isCanceledError(thing) ⇒
boolean
- .delay(ms, value) ⇒
CPromise
- .all(iterable, options) ⇒
CPromise
- .race(thenables) ⇒
CPromise
- .allSettled(iterable, options) ⇒
CPromise
- .from(thing, [resolveSignatures]) ⇒
CPromise
- .isCanceledError(thing) ⇒
new CPromise([executor], [options])
Creates a new CPromise instance
Param | Type | Description |
---|---|---|
[executor] | CPromiseExecutorFn |
promise executor function that will be invoked in the context of the new CPromise instance |
[options] | CPromiseOptions |
cPromise.signal : AbortSignal
get promise abort signal object
Kind: instance property of CPromise
cPromise.isPending ⇒ Boolean
indicates if the promise is pending
Kind: instance property of CPromise
cPromise.isCanceled ⇒ Boolean
indicates if the promise is pending
Kind: instance property of CPromise
cPromise.isCaptured ⇒ Boolean
indicates if the promise progress is captured
Kind: instance property of CPromise
cPromise.isPaused ⇒ Boolean
indicates if the promise is paused
Kind: instance property of CPromise
cPromise.parent ⇒ CPromise
| null
get parent promise
Kind: instance property of CPromise
cPromise.totalWeight([weight]) ⇒ Number
| CPromise
Set or get the total weight of the inner chains
Kind: instance method of CPromise
Param | Type |
---|---|
[weight] | Number |
cPromise.innerWeight([weight]) ⇒ Number
| CPromise
Set or get the total weight of the inner chains
Kind: instance method of CPromise
Param | Type |
---|---|
[weight] | Number |
cPromise.progress([value], [data]) ⇒ Number
| CPromise
Set promise progress
Kind: instance method of CPromise
Param | Type | Description |
---|---|---|
[value] | Number |
a number between [0, 1] |
[data] | * |
any data to send for progress event listeners |
cPromise.propagate(type, data) ⇒ CPromise
emit propagate event that will propagate through each promise scope in the chain (bubbling)
Kind: instance method of CPromise
Param | Type | Default | Description |
---|---|---|---|
type | String | symbol |
some type to identify the data kind | |
data | * |
|
some data |
cPromise.captureProgress([options]) ⇒ CPromise
capture initial progress state of the chain
Kind: instance method of CPromise
Param | Type | Description |
---|---|---|
[options] | Object |
|
options.throttle | Number |
set min interval for firing progress event |
options.innerWeight | Number |
set weight of the nested promises |
cPromise.scopes() ⇒ Array.<CPromise>
Returns all parent scopes that are in pending state
Kind: instance method of CPromise
cPromise.timeout([ms]) ⇒ Number
| CPromise
timeout before the promise will be canceled
Kind: instance method of CPromise
Param | Type | Description |
---|---|---|
[ms] | Number |
timeout in ms |
cPromise.weight([weight]) ⇒ Number
| CPromise
Sets the promise weight in progress capturing process
Kind: instance method of CPromise
Returns: Number
| CPromise
- returns weight if no arguments were specified
Param | Type | Description |
---|---|---|
[weight] | Number |
any number greater or equal 0 |
cPromise.label([label]) ⇒ Number
| CPromise
Sets the promise label
Kind: instance method of CPromise
Returns: Number
| CPromise
- returns weight if no arguments were specified
Param | Type | Description |
---|---|---|
[label] | String |
any string |
cPromise.resolve(value) ⇒ CPromise
Resolves the promise with given value
Kind: instance method of CPromise
Param |
---|
value |
cPromise.reject(err) ⇒ CPromise
Rejects the promise with given error
Kind: instance method of CPromise
Param |
---|
err |
cPromise.pause() ⇒ Boolean
Pause promise
Kind: instance method of CPromise
cPromise.resume() ⇒ Boolean
Resume promise
Kind: instance method of CPromise
cPromise.cancel([reason])
throws the CanceledError that cause promise chain cancellation
Kind: instance method of CPromise
Param | Type |
---|---|
[reason] | String | Error |
cPromise.emitSignal([data], type, [handler]) ⇒ Boolean
Emit a signal of the specific type
Kind: instance method of CPromise
Param | Type |
---|---|
[data] | * |
type | Signal |
[handler] | function |
cPromise.delay(ms) ⇒ CPromise
Returns a chain that will be resolved after specified timeout
Kind: instance method of CPromise
Param | Type |
---|---|
ms | Number |
cPromise.then(onFulfilled, [onRejected]) ⇒ CPromise
returns a CPromise. It takes up to two arguments: callback functions for the success and failure cases of the Promise.
Kind: instance method of CPromise
Param | Type |
---|---|
onFulfilled | onFulfilled |
[onRejected] | onRejected |
cPromise.catch(onRejected, [filter]) ⇒ CPromise
Catches rejection with optionally specified Error class
Kind: instance method of CPromise
Param | Type |
---|---|
onRejected | function |
[filter] | Error |
cPromise.canceled([onCanceled]) ⇒ CPromise
Catches CancelError rejection
Kind: instance method of CPromise
Param | Type |
---|---|
[onCanceled] | function |
cPromise.listen(signal) ⇒ CPromise
Listen for abort signal
Kind: instance method of CPromise
Param | Type |
---|---|
signal | AbortSignal |
cPromise.on(type, listener, [prepend]) ⇒ CPromise
adds a new listener
Kind: instance method of CPromise
Param | Type | Default |
---|---|---|
type | EventType |
|
listener | function |
|
[prepend] | Boolean |
false |
cPromise.off(type, listener) ⇒ CPromise
removes the listener
Kind: instance method of CPromise
Param | Type |
---|---|
type | EventType |
listener | function |
cPromise.listenersCount(type) ⇒ Number
returns listeners count of the specific event type
Kind: instance method of CPromise
Param | Type |
---|---|
type | EventType |
cPromise.hasListeners(type) ⇒ Boolean
checks if there are listeners of a specific type
Kind: instance method of CPromise
Param | Type |
---|---|
type | String | Symbol |
cPromise.once(type, listener) ⇒ CPromise
add 'once' listener
Kind: instance method of CPromise
Param | Type |
---|---|
type | EventType |
listener | function |
cPromise.emit(type, ...args) ⇒ CPromise
emits the event
Kind: instance method of CPromise
Param | Type |
---|---|
type | EventType |
...args |
cPromise.emitHook(type, ...args) ⇒ Boolean
Emits event as a hook. If some listener return true, this method will immediately return true as the result. Else false will be retuned
Kind: instance method of CPromise
Param | Type |
---|---|
type | EventType |
...args |
CPromise.isCanceledError(thing) ⇒ boolean
Checks if thing is an CanceledError instance
Kind: static method of CPromise
Param |
---|
thing |
CPromise.delay(ms, value) ⇒ CPromise
Returns a CPromise that will be resolved after specified timeout
Kind: static method of CPromise
Param | Type | Description |
---|---|---|
ms | Number |
delay before resolve the promise with specified value |
value |
CPromise.all(iterable, options) ⇒ CPromise
Returns a single CPromise that resolves to an array of the results of the input promises. If one fails then other promises will be canceled immediately
Kind: static method of CPromise
Param | Type |
---|---|
iterable | Iterable | Generator | GeneratorFunction |
options | AllOptions |
Example
CPromise.all(function*(){
yield axios.get(url1);
yield axios.get(url2);
yield axios.get(url3);
}, {concurrency: 1}).then(console.log)
CPromise.race(thenables) ⇒ CPromise
returns a promise that fulfills or rejects as soon as one of the promises in an iterable fulfills or rejects, with the value or reason from that promise. Other pending promises will be canceled immediately
Kind: static method of CPromise
Param | Type |
---|---|
thenables | Iterable |
CPromise.allSettled(iterable, options) ⇒ CPromise
returns a promise that resolves after all of the given promises have either fulfilled or rejected
Kind: static method of CPromise
Param | Type |
---|---|
iterable | Iterable | Generator | GeneratorFunction |
options | AllOptions |
CPromise.from(thing, [resolveSignatures]) ⇒ CPromise
Converts thing to CPromise using the following rules:
- CPromise instance returns as is
- Objects with special method defined with key
Symbol.for('toCPromise')
will be converted using this method The result will be cached for future calls - Thenable wraps into a new CPromise instance, if thenable has the
cancel
method it will be used for canceling - Generator function will be resolved to CPromise
- Array will be resoled via
CPromise.all
, arrays with one element (e.g.[[1000]]
) will be resolved viaCPromise.race
This method returns null if the conversion failed.
Kind: static method of CPromise
Param | Type | Default |
---|---|---|
thing | * |
|
[resolveSignatures] | boolean |
true |
CPromise~EventType : String
| Symbol
Kind: inner typedef of CPromise
CPromise~CPromiseExecutorFn : function
Kind: inner typedef of CPromise
this: CPromise
Param | Type |
---|---|
resolve | function |
reject | function |
scope | CPromise |
CPromise~PromiseOptionsObject : Object
Kind: inner typedef of CPromise
Properties
Name | Type |
---|---|
label | String |
timeout | Number |
weight | Number |
CPromise~CPromiseOptions : PromiseOptionsObject
| String
| Number
If value is a number it will be considered as the value for timeout option If value is a string it will be considered as a label
Kind: inner typedef of CPromise
CPromise~OnCancelListener : function
Kind: inner typedef of CPromise
Param | Type |
---|---|
reason | CanceledError |
CPromise~OnPauseListener : function
Kind: inner typedef of CPromise
CPromise~OnResumeListener : function
Kind: inner typedef of CPromise
CPromise~Signal : String
| Signal
Kind: inner typedef of CPromise
CPromise~SignalHandler ⇒ Boolean
Kind: inner typedef of CPromise
this: {CPromise}
Param | Type |
---|---|
type | Signal |
data | * |
scope | CPromise |
CPromise~AllOptions : object
Kind: inner typedef of CPromise
Properties
Name | Type | Description |
---|---|---|
concurrency | number |
limit concurrency of promise being run simultaneously |
mapper | function |
function to map each element |
ignoreResults | boolean |
do not collect results |
signatures | boolean |
use advanced signatures for vales resolving |
License
The MIT License Copyright (c) 2020 Dmitriy Mozgovoy robotshara@gmail.com
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.