Skip to content

Commit

Permalink
DestroyableCanceledPromise impl (#26)
Browse files Browse the repository at this point in the history
  • Loading branch information
snewcomer committed Nov 10, 2021
1 parent 933e311 commit c2d53c2
Show file tree
Hide file tree
Showing 5 changed files with 56 additions and 7 deletions.
26 changes: 22 additions & 4 deletions addon/decorators/stateful-function.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { StatefulPromise } from 'ember-stateful-promise/utils/stateful-promise';
import { CanceledPromise } from 'ember-stateful-promise/utils/canceled-promise';
import { DestroyableCanceledPromise } from 'ember-stateful-promise/utils/destroyable-canceled-promise';
import { tracked } from '@glimmer/tracking';

class Handler {
Expand All @@ -13,6 +14,15 @@ class Handler {

cancelPromise = false;

reset() {
this.isCanceled = false;
this.isResolved = false;
this.isRunning = false;
this.isError = false;

this.performCount = 0;
}

cancel() {
this.cancelPromise = true;
}
Expand All @@ -39,10 +49,10 @@ class Handler {
export function statefulFunction(options) {
const throttle = options.throttle;
let ctx;
const decorator = function (target, _property, descriptor) {
const decorator = function (_target, _property, descriptor) {
const actualFunc = descriptor.value;

const handler = new Handler();
let handler = new Handler();
let rej;

const _statefulFunc = function (...args) {
Expand All @@ -61,7 +71,7 @@ export function statefulFunction(options) {

let maybePromise = actualFunc.call(ctx, ...args);
// wrapping the promise in a StatefulPromise
const sp = new StatefulPromise().create(target, (resolveFn, rejectFn) => {
const sp = new StatefulPromise().create(ctx, (resolveFn, rejectFn) => {
// store away in case we need to cancel
rej = rejectFn;

Expand All @@ -82,6 +92,9 @@ export function statefulFunction(options) {
}
})
.catch((e) => {
if (e instanceof DestroyableCanceledPromise) {
handler.reset();
}
rej(e);
})
.finally(() => {
Expand All @@ -98,7 +111,12 @@ export function statefulFunction(options) {

sp.catch((e) => {
// ensure no unhandledrejection if canceled
if (!(e instanceof CanceledPromise)) {
if (
!(
e instanceof CanceledPromise ||
e instanceof DestroyableCanceledPromise
)
) {
throw e;
}
});
Expand Down
7 changes: 7 additions & 0 deletions addon/utils/destroyable-canceled-promise.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export class DestroyableCanceledPromise extends Error {
constructor(msg) {
super(msg);

Object.setPrototypeOf(this, DestroyableCanceledPromise.prototype);
}
}
11 changes: 8 additions & 3 deletions addon/utils/stateful-promise.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { tracked } from '@glimmer/tracking';
import { registerDestructor, isDestroying } from '@ember/destroyable';
import { CanceledPromise } from 'ember-stateful-promise/utils/canceled-promise';
import { DestroyableCanceledPromise } from 'ember-stateful-promise/utils/destroyable-canceled-promise';

export class StatefulPromise extends Promise {
/**
Expand Down Expand Up @@ -71,7 +72,7 @@ export class StatefulPromise extends Promise {
if (this._state === 'RUNNING') {
this._state = 'CANCELED';
this._reject(
new CanceledPromise(
new DestroyableCanceledPromise(
'The object this promise was attached to was destroyed'
)
);
Expand All @@ -93,7 +94,7 @@ export class StatefulPromise extends Promise {
this._state = 'CANCELED';
this._reject(
new CanceledPromise(
'The object this promise was attached to was destroyed'
'The object this promise was attached to was destroyed while the promise was still outstanding'
)
);
} else {
Expand All @@ -104,6 +105,8 @@ export class StatefulPromise extends Promise {
.catch((e) => {
if (e instanceof CanceledPromise) {
this._state = 'CANCELED';
} else if (e instanceof DestroyableCanceledPromise) {
this._state = 'CANCELED';
} else {
this._state = 'ERROR';
}
Expand All @@ -116,7 +119,7 @@ export class StatefulPromise extends Promise {
if (isDestroying(destroyable)) {
this._state = 'CANCELED';
this._reject(
new CanceledPromise(
new DestroyableCanceledPromise(
'The object this promise was attached to was destroyed'
)
);
Expand All @@ -129,6 +132,8 @@ export class StatefulPromise extends Promise {
(err) => {
if (err instanceof CanceledPromise) {
this._state = 'CANCELED';
} else if (err instanceof DestroyableCanceledPromise) {
this._state = 'CANCELED';
} else {
this._state = 'ERROR';
}
Expand Down
1 change: 1 addition & 0 deletions app/utils/destroyable-canceled-promise.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from 'ember-stateful-promise/utils/destroyable-canceled-promise';
18 changes: 18 additions & 0 deletions tests/integration/components/playground-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,24 @@ import { hbs } from 'ember-cli-htmlbars';
module('Integration | Component | playground', function (hooks) {
setupRenderingTest(hooks);

test('it renders - setup state', async function (assert) {
await render(hbs`<Playground />`);
assert
.dom('[data-test-playground-perform-count]')
.hasText('Perform Count: 0');

click('[data-test-playground-button]');
click('[data-test-playground-button]');

await waitFor('[data-test-playground-button]:is([disabled])');
assert.dom('[data-test-playground-button]').hasAttribute('disabled');

assert
.dom('[data-test-playground-perform-count]')
.hasText('Perform Count: 2');
// button is disabled and promise needs to be cancelled when component is destroyed
});

test('it renders', async function (assert) {
await render(hbs`<Playground />`);
assert
Expand Down

0 comments on commit c2d53c2

Please sign in to comment.