diff --git a/addon/decorators/stateful-function.js b/addon/decorators/stateful-function.js
index 98557c1..1edd0a0 100644
--- a/addon/decorators/stateful-function.js
+++ b/addon/decorators/stateful-function.js
@@ -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 {
@@ -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;
}
@@ -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) {
@@ -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;
@@ -82,6 +92,9 @@ export function statefulFunction(options) {
}
})
.catch((e) => {
+ if (e instanceof DestroyableCanceledPromise) {
+ handler.reset();
+ }
rej(e);
})
.finally(() => {
@@ -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;
}
});
diff --git a/addon/utils/destroyable-canceled-promise.js b/addon/utils/destroyable-canceled-promise.js
new file mode 100644
index 0000000..6194bca
--- /dev/null
+++ b/addon/utils/destroyable-canceled-promise.js
@@ -0,0 +1,7 @@
+export class DestroyableCanceledPromise extends Error {
+ constructor(msg) {
+ super(msg);
+
+ Object.setPrototypeOf(this, DestroyableCanceledPromise.prototype);
+ }
+}
diff --git a/addon/utils/stateful-promise.js b/addon/utils/stateful-promise.js
index 603d1b2..50c775e 100644
--- a/addon/utils/stateful-promise.js
+++ b/addon/utils/stateful-promise.js
@@ -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 {
/**
@@ -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'
)
);
@@ -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 {
@@ -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';
}
@@ -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'
)
);
@@ -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';
}
diff --git a/app/utils/destroyable-canceled-promise.js b/app/utils/destroyable-canceled-promise.js
new file mode 100644
index 0000000..21e6405
--- /dev/null
+++ b/app/utils/destroyable-canceled-promise.js
@@ -0,0 +1 @@
+export { default } from 'ember-stateful-promise/utils/destroyable-canceled-promise';
diff --git a/tests/integration/components/playground-test.js b/tests/integration/components/playground-test.js
index 05d5018..28cda32 100644
--- a/tests/integration/components/playground-test.js
+++ b/tests/integration/components/playground-test.js
@@ -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``);
+ 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``);
assert