Skip to content

Commit

Permalink
Add ability to not reject on cancel (#11)
Browse files Browse the repository at this point in the history
  • Loading branch information
szmarczak authored and sindresorhus committed Sep 10, 2018
1 parent 54b12bb commit a3c22ab
Show file tree
Hide file tree
Showing 4 changed files with 94 additions and 21 deletions.
40 changes: 27 additions & 13 deletions index.js
Expand Up @@ -25,23 +25,35 @@ class PCancelable {
this._cancelHandlers = [];
this._isPending = true;
this._isCanceled = false;
this._rejectOnCancel = true;

this._promise = new Promise((resolve, reject) => {
this._reject = reject;

return executor(
value => {
this._isPending = false;
resolve(value);
},
error => {
this._isPending = false;
reject(error);
},
handler => {
this._cancelHandlers.push(handler);
const onResolve = value => {
this._isPending = false;
resolve(value);
};

const onReject = error => {
this._isPending = false;
reject(error);
};

const onCancel = handler => {
this._cancelHandlers.push(handler);
};

Object.defineProperties(onCancel, {
shouldReject: {
get: () => this._rejectOnCancel,
set: bool => {
this._rejectOnCancel = bool;
}
}
);
});

return executor(onResolve, onReject, onCancel);
});
}

Expand Down Expand Up @@ -73,7 +85,9 @@ class PCancelable {
}

this._isCanceled = true;
this._reject(new CancelError(reason));
if (this._rejectOnCancel) {
this._reject(new CancelError(reason));
}
}

get isCanceled() {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -42,6 +42,6 @@
"ava": "*",
"delay": "^3.0.0",
"promise.prototype.finally": "^3.1.0",
"xo": "*"
"xo": "^0.23.0"
}
}
18 changes: 17 additions & 1 deletion readme.md
Expand Up @@ -53,7 +53,23 @@ setTimeout(() => {

### new PCancelable(executor)

Same as the [`Promise` constructor](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise), but with an appended `onCancel` parameter in `executor`.
Same as the [`Promise` constructor](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise), but with an appended `onCancel` parameter in `executor`.<br>
Cancelling will reject the promise with `PCancelable.CancelError`. To avoid that, set `onCancel.shouldReject` to `false`.

```js
const cancelable = new PCancelable((resolve, reject, onCancel) => {
const job = new Job();

onCancel.shouldReject = false;
onCancel(() => {
job.stop();
});

job.on('finish', resolve);
});

promise.cancel(); // Doesn't throw an error
```

`PCancelable` is a subclass of `Promise`.

Expand Down
55 changes: 49 additions & 6 deletions test.js
Expand Up @@ -49,9 +49,9 @@ test('calling `.cancel()` multiple times', async t => {

try {
await p;
} catch (err) {
} catch (error) {
p.cancel();
t.true(err instanceof PCancelable.CancelError);
t.true(error instanceof PCancelable.CancelError);
}
});

Expand Down Expand Up @@ -190,14 +190,57 @@ test('default message with no reason', async t => {
const p = new PCancelable(() => {});
p.cancel();

const err = await t.throws(p);
t.is(err.message, 'Promise was canceled');
await t.throws(p, 'Promise was canceled');
});

test('custom reason', async t => {
const p = new PCancelable(() => {});
p.cancel('unicorn');

const err = await t.throws(p);
t.is(err.message, 'unicorn');
await t.throws(p, 'unicorn');
});

test('prevent rejection', async t => {
const p = new PCancelable((resolve, reject, onCancel) => {
onCancel.shouldReject = false;
setTimeout(resolve, 100);
});

p.cancel();
await t.notThrows(p);
});

test('prevent rejection and reject later', async t => {
const p = new PCancelable((resolve, reject, onCancel) => {
onCancel.shouldReject = false;
setTimeout(() => reject(new Error('unicorn')), 100);
});

p.cancel();
await t.throws(p, 'unicorn');
});

test('prevent rejection and resolve later', async t => {
const p = new PCancelable((resolve, reject, onCancel) => {
onCancel.shouldReject = false;
setTimeout(() => resolve('unicorn'), 100);
});

t.is(await p, 'unicorn');
});

test('`onCancel.shouldReject` is true by default', async t => {
await t.notThrows(() => new PCancelable((resolve, reject, onCancel) => {
t.true(onCancel.shouldReject);
}));
});

test('throws on cancel when `onCancel.shouldReject` is true', async t => {
const p = new PCancelable((resolve, reject, onCancel) => {
onCancel.shouldReject = false;
onCancel.shouldReject = true;
});
p.cancel();

await t.throws(p);
});

0 comments on commit a3c22ab

Please sign in to comment.