Skip to content

Commit

Permalink
Add .cancel() (#17)
Browse files Browse the repository at this point in the history
  • Loading branch information
timdp authored and sindresorhus committed Mar 22, 2017
1 parent 0b0d22a commit 90d16f2
Show file tree
Hide file tree
Showing 4 changed files with 57 additions and 16 deletions.
44 changes: 29 additions & 15 deletions index.js
@@ -1,34 +1,48 @@
'use strict';
const defer = require('p-defer');

class CancelError extends Error {
constructor(message) {
super(message);
this.name = 'CancelError';
}
}

const generate = willResolve => function (ms, value) {
ms = ms || 0;
const useValue = (arguments.length > 1);
let result = value;

// If supplied, the thunk will override promise results with `value`.
const useValue = arguments.length > 1;
const delaying = defer();
const promise = delaying.promise;

let promise;
let timeout = setTimeout(() => {
const settle = willResolve ? delaying.resolve : delaying.reject;
settle(result);
}, ms);

const thunk = result => {
if (promise) {
// Prevent unhandled rejection errors if promise is never used, and only used as a thunk
promise.catch(() => {});
const thunk = thunkResult => {
if (!useValue) {
result = thunkResult;
}
return new Promise((resolve, reject) => {
setTimeout(
willResolve ? resolve : reject,
ms,
useValue ? value : result
);
});
return promise;
};

promise = thunk(value);
thunk.then = promise.then.bind(promise);
thunk.catch = promise.catch.bind(promise);
thunk._actualPromise = promise;

thunk.cancel = () => {
if (timeout) {
clearTimeout(timeout);
timeout = null;
delaying.reject(new CancelError('Delay canceled'));
}
};

return thunk;
};

module.exports = generate(true);
module.exports.reject = generate(false);
module.exports.CancelError = CancelError;
3 changes: 3 additions & 0 deletions package.json
Expand Up @@ -37,6 +37,9 @@
"promises",
"bluebird"
],
"dependencies": {
"p-defer": "^1.0.0"
},
"devDependencies": {
"ava": "*",
"bluebird": "^3.3.5",
Expand Down
13 changes: 13 additions & 0 deletions readme.md
Expand Up @@ -54,6 +54,19 @@ Promise.resolve('foo')
// executed 100 milliseconds later
// err === 'bar'
});

// you can cancel the promise by calling .cancel()
async () => {
const delaying = delay(1000);
setTimeout(() => {
delaying.cancel();
}, 500);
try {
await delaying;
} catch (err) {
// err is an instance of delay.CancelError
}
}();
```


Expand Down
13 changes: 12 additions & 1 deletion test.js
Expand Up @@ -3,7 +3,7 @@ import timeSpan from 'time-span';
import inRange from 'in-range';
// TODO: this is deprecated, switch it out with the `currently-unhandled` module
import trackRejections from 'loud-rejection/api';
import m from './';
import m, {CancelError} from './';

// install core-js promise globally, because Node 0.12 native promises don't generate unhandledRejection events
global.Promise = Promise;
Expand Down Expand Up @@ -119,3 +119,14 @@ test.failing('rejected.then(rejectThunk).catch(handler) - should not create unha

tracker.currentlyUnhandled().forEach(({promise}) => promise.catch(() => {}));
});

test('can be canceled', async t => {
const delaying = m(1000);
delaying.cancel();
try {
await delaying;
t.fail();
} catch (err) {
t.true(err instanceof CancelError);
}
});

0 comments on commit 90d16f2

Please sign in to comment.