Skip to content

Commit

Permalink
Added outer abort signal support;
Browse files Browse the repository at this point in the history
  • Loading branch information
DigitalBrainJS committed Sep 19, 2020
1 parent 137360b commit ecf8905
Show file tree
Hide file tree
Showing 3 changed files with 101 additions and 26 deletions.
38 changes: 27 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -264,8 +264,9 @@ Cancellable Promise with extra features
* [.label(label)](#module_CPromise..CPromiseScope+label) ⇒ <code>Number</code> \| <code>CPromiseScope</code>
* [.resolve(value)](#module_CPromise..CPromiseScope+resolve)
* [.reject(err)](#module_CPromise..CPromiseScope+reject)
* [._done(err, value, [reject])](#module_CPromise..CPromiseScope+_done)
* [.done(err, value)](#module_CPromise..CPromiseScope+done)
* [.cancel(reason)](#module_CPromise..CPromiseScope+cancel)
* [.cancel([reason])](#module_CPromise..CPromiseScope+cancel)
* _static_
* [.execute(executor, resolve, reject, options)](#module_CPromise..CPromiseScope.execute) ⇒ <code>CPromiseScope</code>
* [~CPromise](#module_CPromise..CPromise) ⇐ <code>Promise</code>
Expand All @@ -292,7 +293,7 @@ Cancellable Promise with extra features
* [~OnCancelListener](#module_CPromise..OnCancelListener) : <code>function</code>
* [~CPromiseExecutorFn](#module_CPromise..CPromiseExecutorFn) : <code>function</code>
* [~CPromiseExecutorFn](#module_CPromise..CPromiseExecutorFn) : <code>function</code>
* [~CPromiseOptions](#module_CPromise..CPromiseOptions) : <code>Object</code> \| <code>String</code> \| <code>Number</code>
* [~CPromiseOptions](#module_CPromise..CPromiseOptions) : <code>PromiseScopeOptions</code> \| <code>String</code> \| <code>Number</code>

<a name="module_CPromise..AsyncGeneratorScope"></a>

Expand Down Expand Up @@ -364,8 +365,9 @@ Scope for CPromises instances
* [.label(label)](#module_CPromise..CPromiseScope+label) ⇒ <code>Number</code> \| <code>CPromiseScope</code>
* [.resolve(value)](#module_CPromise..CPromiseScope+resolve)
* [.reject(err)](#module_CPromise..CPromiseScope+reject)
* [._done(err, value, [reject])](#module_CPromise..CPromiseScope+_done)
* [.done(err, value)](#module_CPromise..CPromiseScope+done)
* [.cancel(reason)](#module_CPromise..CPromiseScope+cancel)
* [.cancel([reason])](#module_CPromise..CPromiseScope+cancel)
* _static_
* [.execute(executor, resolve, reject, options)](#module_CPromise..CPromiseScope.execute) ⇒ <code>CPromiseScope</code>

Expand Down Expand Up @@ -514,6 +516,19 @@ Rejects the promise with given error
| --- |
| err |

<a name="module_CPromise..CPromiseScope+_done"></a>

#### cPromiseScope.\_done(err, value, [reject])
Resolves or rejects the promise depending on the arguments

**Kind**: instance method of [<code>CPromiseScope</code>](#module_CPromise..CPromiseScope)

| Param | Type | Description |
| --- | --- | --- |
| err | | error object, if specified the promise will be rejected with this error, resolves otherwise |
| value | | |
| [reject] | <code>boolean</code> | |

<a name="module_CPromise..CPromiseScope+done"></a>

#### cPromiseScope.done(err, value)
Expand All @@ -528,14 +543,14 @@ Resolves or rejects the promise depending on the arguments

<a name="module_CPromise..CPromiseScope+cancel"></a>

#### cPromiseScope.cancel(reason)
#### cPromiseScope.cancel([reason])
throws the CanceledError that cause promise chain cancellation

**Kind**: instance method of [<code>CPromiseScope</code>](#module_CPromise..CPromiseScope)

| Param | Type |
| --- | --- |
| reason | <code>String</code> \| <code>Error</code> |
| [reason] | <code>String</code> \| <code>Error</code> |

<a name="module_CPromise..CPromiseScope.execute"></a>

Expand Down Expand Up @@ -738,11 +753,12 @@ Converts thing to CPromise using the following rules: - CPromise instance return
**Kind**: inner typedef of [<code>CPromise</code>](#module_CPromise)
**Properties**

| Name | Type | Description |
| --- | --- | --- |
| label | <code>String</code> | the label for the promise |
| weight= | <code>Number</code> | 1 - the progress weight of the promise |
| timeout= | <code>Number</code> | 0 - max pending time |
| Name | Type | Default | Description |
| --- | --- | --- | --- |
| label | <code>String</code> | | the label for the promise |
| weight | <code>Number</code> | <code>1</code> | the progress weight of the promise |
| timeout | <code>Number</code> | <code>0</code> | max pending time |
| signal | <code>AbortSignal</code> | | AbortController signal |

<a name="module_CPromise..onFulfilled"></a>

Expand Down Expand Up @@ -799,7 +815,7 @@ Converts thing to CPromise using the following rules: - CPromise instance return

<a name="module_CPromise..CPromiseOptions"></a>

### CPromise~CPromiseOptions : <code>Object</code> \| <code>String</code> \| <code>Number</code>
### CPromise~CPromiseOptions : <code>PromiseScopeOptions</code> \| <code>String</code> \| <code>Number</code>
If value is a number it will be considered as the value for timeout optionIf value is a string it will be considered as label

**Kind**: inner typedef of [<code>CPromise</code>](#module_CPromise)
Expand Down
65 changes: 51 additions & 14 deletions lib/c-promise.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,11 @@ const _isChain = Symbol('isChain');
const _timer = Symbol('timer');
const _timeout = Symbol('timeout');
const _objectToCPromise = Symbol('objectToCPromise');
const _done= Symbol('done');
const TYPE_PROGRESS = Symbol('TYPE_PROGRESS');



const promiseAssocStore= new WeakMap();

const _toCPromise= Symbol.for('toCPromise');
Expand Down Expand Up @@ -63,6 +66,14 @@ function isGenerator(thing) {

const iterableToArray = Array.from ? Array.from : (iterable) => Array.prototype.slice.call(iterable);

const isAbortSignal = (thing) => {
return thing &&
typeof thing === 'object' &&
typeof thing.aborted === 'boolean' &&
typeof thing.addEventListener === 'function' &&
typeof thing.removeEventListener === 'function';
}

/**
* Scope for generator resolvers
*/
Expand Down Expand Up @@ -208,9 +219,10 @@ function resolveGenerator(generatorFn, args) {

/**
* @typedef PromiseScopeOptions {Object}
* @property {String} label - the label for the promise
* @property {Number} weight= 1 - the progress weight of the promise
* @property {Number} timeout= 0 - max pending time
* @property {String} label the label for the promise
* @property {Number} weight=1 the progress weight of the promise
* @property {Number} timeout=0 max pending time
* @property {AbortSignal} signal AbortController signal
*/

/**
Expand Down Expand Up @@ -239,8 +251,22 @@ class CPromiseScope extends TinyEventEmitter {
* @param {Function} reject
* @param {PromiseScopeOptions} options
*/
constructor(resolve, reject, {label, weight, timeout} = {}) {
constructor(resolve, reject, {label, weight, timeout, signal} = {}) {
super();

if (signal !== undefined) {
if (!isAbortSignal(signal)) {
throw TypeError('signal should implement AbortSignal interface');
}
const signalListener = () => {
this.cancel();
}
signal.addEventListener('abort', signalListener);
this.on('done', () => {
signal.removeEventListener('abort', signalListener);
})
}

const shadow = this[_shadow] = {
captured: false,
isListening: false,
Expand Down Expand Up @@ -591,11 +617,11 @@ class CPromiseScope extends TinyEventEmitter {
this[_attachScope](value[_scope], true);
}
value.then(
(value) => this.done(null, value),
(err) => this.done(err)
(value) => this[_done](null, value),
(err) => this[_done](err, undefined, true)
)
} else {
this.done(null, value);
this[_done](null, value);
}
}

Expand All @@ -605,28 +631,29 @@ class CPromiseScope extends TinyEventEmitter {
*/

reject(err) {
this.done(err);
this[_done](err, undefined, true);
}

/**
* Resolves or rejects the promise depending on the arguments
* @param err - error object, if specified the promise will be rejected with this error, resolves otherwise
* @param value
* @param {boolean} [reject]
*/

done(err, value) {
[_done](err, value, reject) {
if (!this[_isPending]) return;
this[_isPending] = false;

this[_timer] && clearTimeout(this[_timer]);

if (err) {
if (err instanceof CanceledError) {
if (err || reject) {
if (err && err instanceof CanceledError) {
this[_handleCancelRejection](err);
}
this.emit('done', err);
this[_reject](err);
} else {
}else {
this[_shadow].captured && this.progress(1);
this.emit('done', undefined, value);
this[_resolve](value);
Expand All @@ -638,6 +665,16 @@ class CPromiseScope extends TinyEventEmitter {
this[_parent] = null;
}

/**
* Resolves or rejects the promise depending on the arguments
* @param err - error object, if specified the promise will be rejected with this error, resolves otherwise
* @param value
*/

done(err, value){
return this[_done](err, value);
}

[_handleCancelRejection](err) {
this[_isCanceled] = true;

Expand All @@ -653,7 +690,7 @@ class CPromiseScope extends TinyEventEmitter {

/**
* throws the CanceledError that cause promise chain cancellation
* @param {String|Error} reason
* @param {String|Error} [reason]
*/

cancel(reason) {
Expand Down Expand Up @@ -728,7 +765,7 @@ class CPromiseScope extends TinyEventEmitter {
/**
* 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 label
* @typedef {Object|String|Number} CPromiseOptions
* @typedef {PromiseScopeOptions|String|Number} CPromiseOptions
*/

/**
Expand Down
24 changes: 23 additions & 1 deletion test/tests/CPromise.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,28 @@ module.exports = {
}
},

'should support cancallation by the external signal': async function(){
const controller = new CPromise.AbortController();

const timestamp = Date.now();
const time = () => Date.now() - timestamp;

setTimeout(() => controller.abort(), 55);

return new CPromise((resolve, reject) => {
setTimeout(resolve, 100);
}, {signal: controller.signal}).then(() => {
throw Error('not cancelled');
}, (err) => {
if (!CPromise.isCanceledError(err)) {
if (time() < 50) {
throw Error('Early cancellation');
}
throw err;
}
})
},

'prototype.cancel()': {
'should reject the promise with CanceledError': async function () {
const promise = new CPromise((resolve, reject) => {
Expand Down Expand Up @@ -79,7 +101,7 @@ module.exports = {
assert.fail('promise has not been canceled');
}, (err) => {
if (err instanceof CPromise.CanceledError) {
if (Date.now() - timestamp < timeout) {
if (Date.now() - timestamp < timeout - 5) {
assert.fail('early cancellation detected')
}
if (currentChain !== targetChainIndex) {
Expand Down

0 comments on commit ecf8905

Please sign in to comment.