From e8f634bcb3dbef05e79e5ae7667db7de49a9d886 Mon Sep 17 00:00:00 2001
From: Max Fierke <max@maxfierke.com>
Date: Sat, 18 Sep 2021 21:18:28 -0500
Subject: [PATCH] Add onError parameter to perform helper

Allows setting to reporting function for reporting to error handlers, etc.
Also allows setting to null to swallow all errors
---
 addon/helpers/perform.js                  | 32 +++++++++++-
 tests/integration/helpers/perform-test.js | 62 +++++++++++++++++++++++
 2 files changed, 93 insertions(+), 1 deletion(-)

diff --git a/addon/helpers/perform.js b/addon/helpers/perform.js
index b144a57df..86a140e72 100644
--- a/addon/helpers/perform.js
+++ b/addon/helpers/perform.js
@@ -1,8 +1,38 @@
 import { helper } from '@ember/component/helper';
+import { assert } from '@ember/debug';
 import { taskHelperClosure } from 'ember-concurrency/-private/helpers';
 
+function maybeReportError(onError) {
+  return function (e) {
+    if (typeof onError === 'function') {
+      onError(e);
+    } else if (onError === null) {
+      // Do nothing
+    } else {
+      assert(
+        `The onError argument passed to the \`perform\` helper should be a function or null; you passed ${onError}`,
+        false
+      );
+    }
+  };
+}
+
 export function performHelper(args, hash) {
-  return taskHelperClosure('perform', 'perform', args, hash);
+  let perform = taskHelperClosure('perform', 'perform', args, hash);
+
+  if (hash && typeof hash.onError !== 'undefined') {
+    return function (...innerArgs) {
+      try {
+        let taskInstance = perform(...innerArgs);
+        return taskInstance.catch(maybeReportError(hash.onError));
+        // eslint-disable-next-line no-empty
+      } catch {
+        maybeReportError(hash.onError);
+      }
+    };
+  } else {
+    return perform;
+  }
 }
 
 export default helper(performHelper);
diff --git a/tests/integration/helpers/perform-test.js b/tests/integration/helpers/perform-test.js
index e3d29900d..7c6c49776 100644
--- a/tests/integration/helpers/perform-test.js
+++ b/tests/integration/helpers/perform-test.js
@@ -35,4 +35,66 @@ module('Integration | helpers | perform', function (hooks) {
 
     assert.ok(true);
   });
+
+  test('can pass onError=null to have it swallow errors thrown from task', async function (assert) {
+    assert.expect(1);
+
+    this.owner.register(
+      'component:test-swallow-error',
+      Component.extend({
+        errorGeneratingTask: task(function* () {
+          throw new Error('You should not see me!');
+        }),
+      })
+    );
+
+    this.owner.register(
+      'template:components/test-swallow-error',
+      hbs`
+      <button {{on 'click' (perform this.errorGeneratingTask onError=null)}}>
+        I create an error!
+      </button>
+    `
+    );
+
+    await render(hbs`<TestSwallowError />`);
+
+    await click('button');
+
+    assert.ok(true);
+  });
+
+  test('can pass onError=someFn to have it call someFn(e)', async function (assert) {
+    assert.expect(2);
+
+    let error = null;
+
+    this.owner.register(
+      'component:test-swallow-error',
+      Component.extend({
+        errorGeneratingTask: task(function* () {
+          throw new Error('You should not see me!');
+        }),
+        errorReport(e) {
+          error = e;
+        },
+      })
+    );
+
+    this.owner.register(
+      'template:components/test-swallow-error',
+      hbs`
+      <button {{on 'click' (perform this.errorGeneratingTask onError=this.errorReport)}}>
+        I create an error!
+      </button>
+    `
+    );
+
+    await render(hbs`<TestSwallowError />`);
+
+    await click('button');
+
+    assert.ok(true);
+    assert.ok(error instanceof Error);
+  });
 });