Skip to content

Commit 3671a64

Browse files
authored
Add runtime assertion that async fn transpiled (#476)
1 parent c3ec012 commit 3671a64

File tree

6 files changed

+66
-23
lines changed

6 files changed

+66
-23
lines changed

.github/workflows/ci.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ jobs:
147147
timeout-minutes: 15
148148

149149
strategy:
150-
fail-fast: true
150+
fail-fast: false
151151
matrix:
152152
ember-try-scenario:
153153
- ember-lts-3.8

CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
# Changelog
22

3+
### master
4+
- Add runtime assertion to detect transpilation failures with the new async arrow fn API
5+
36
### 2.3.0
47
- Introduce async-arrow task() API as the new universal JS/TS API, e.g.
58
`myTask = task(this, { drop: true }, async (arg: string) => {})`. This new API

addon/-private/task-public-api.js

+10
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
task as taskDecorator,
1010
taskGroup as taskGroupDecorator,
1111
} from './task-decorators';
12+
import { assert } from '@ember/debug';
1213

1314
/**
1415
* TODO: update docs to reflect both old and new ES6 styles
@@ -58,6 +59,11 @@ import {
5859
* @returns {TaskProperty}
5960
*/
6061
export function task(taskFnOrProtoOrDecoratorOptions, key, descriptor) {
62+
assert(
63+
`It appears you're attempting to use the new task(this, async () => { ... }) syntax, but the async arrow task function you've provided is not being properly compiled by Babel.\n\nPossible causes / remedies:\n\n1. You must pass the async function expression directly to the task() function (it is not currently supported to pass in a variable containing the async arrow fn, or any other kind of indirection)\n2. If this code is in an addon, please ensure the addon specificies ember-concurrency "2.3.0" or higher in "dependencies" (not "devDependencies")\n3. Ensure that there is only one version of ember-concurrency v2.3.0+ being used in your project (including nested dependencies) and consider using npm/yarn/pnpm resolutions to enforce a single version is used`,
64+
!isUntranspiledAsyncFn(arguments[arguments.length - 1])
65+
);
66+
6167
if (
6268
isDecoratorOptions(taskFnOrProtoOrDecoratorOptions) ||
6369
(key && descriptor)
@@ -68,6 +74,10 @@ export function task(taskFnOrProtoOrDecoratorOptions, key, descriptor) {
6874
}
6975
}
7076

77+
function isUntranspiledAsyncFn(obj) {
78+
return obj && obj.constructor && obj.constructor.name === 'AsyncFunction';
79+
}
80+
7181
/**
7282
* Build and return a "classic" TaskProperty, which is essentially a subclass of a Computed Property
7383
* descriptor that can be used to define Tasks on classic Ember.Objects.

tests/acceptance/helpers-test.js

+2-22
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,8 @@
11
import { click, visit, currentURL } from '@ember/test-helpers';
2-
import Ember from 'ember';
32
import { setupApplicationTest } from 'ember-qunit';
43
import { module, skip, test } from 'qunit';
5-
import {
6-
macroCondition,
7-
dependencySatisfies,
8-
importSync,
9-
} from '@embroider/macros';
10-
11-
function getDebugFunction(type) {
12-
if (macroCondition(dependencySatisfies('ember-source', '^3.26.0'))) {
13-
return importSync('@ember/debug').getDebugFunction(type);
14-
} else {
15-
return Ember[type];
16-
}
17-
}
18-
19-
function setDebugFunction(type, fn) {
20-
if (macroCondition(dependencySatisfies('ember-source', '^3.26.0'))) {
21-
return importSync('@ember/debug').setDebugFunction(type, fn);
22-
} else {
23-
Ember[type] = fn;
24-
}
25-
}
4+
import { macroCondition, dependencySatisfies } from '@embroider/macros';
5+
import { getDebugFunction, setDebugFunction } from '../helpers/helpers';
266

277
const originalAssert = getDebugFunction('assert');
288

tests/helpers/helpers.js

+21
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,31 @@
11
import { skip, test } from 'qunit';
22
import { gte } from 'ember-compatibility-helpers';
33
import Ember from 'ember';
4+
import {
5+
importSync,
6+
macroCondition,
7+
dependencySatisfies,
8+
} from '@embroider/macros';
49

510
export const decoratorTest = gte('3.10.0') ? test : skip;
611

712
export function makeAsyncError(hooks) {
813
hooks.afterEach(() => (Ember.onerror = null));
914
return () => new window.Promise((r) => (Ember.onerror = r));
1015
}
16+
17+
export function getDebugFunction(type) {
18+
if (macroCondition(dependencySatisfies('ember-source', '>3.26.0'))) {
19+
return importSync('@ember/debug').getDebugFunction(type);
20+
} else {
21+
return Ember[type];
22+
}
23+
}
24+
25+
export function setDebugFunction(type, fn) {
26+
if (macroCondition(dependencySatisfies('ember-source', '>3.26.0'))) {
27+
return importSync('@ember/debug').setDebugFunction(type, fn);
28+
} else {
29+
Ember[type] = fn;
30+
}
31+
}

tests/integration/async-arrow-task-test.js

+29
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
enqueueTask,
1313
} from 'ember-concurrency';
1414
import Component from '@glimmer/component';
15+
import { getDebugFunction, setDebugFunction } from '../helpers/helpers';
1516

1617
function defer() {
1718
let resolve, reject;
@@ -24,6 +25,8 @@ function defer() {
2425
return { promise, resolve, reject };
2526
}
2627

28+
const originalAssert = getDebugFunction('assert');
29+
2730
class TestComponent extends Component {
2831
resolved = null;
2932

@@ -89,6 +92,10 @@ module('Integration | async-arrow-task', function (hooks) {
8992
);
9093
});
9194

95+
hooks.afterEach(function () {
96+
setDebugFunction('assert', originalAssert);
97+
});
98+
9299
test('two args - task(this, async () => {})', async function (assert) {
93100
assert.expect(13);
94101

@@ -167,4 +174,26 @@ module('Integration | async-arrow-task', function (hooks) {
167174

168175
await finishTest(assert);
169176
});
177+
178+
test('runtime assertion to detect improper task() use or transpilation errors', async function (assert) {
179+
assert.expect(2);
180+
181+
let assertionDidFire = false;
182+
setDebugFunction('assert', function (msg, test) {
183+
if (!test) {
184+
// eslint-disable-next-line qunit/no-conditional-assertions
185+
assert.ok(
186+
msg.includes(
187+
"the async arrow task function you've provided is not being properly compiled by Babel"
188+
),
189+
'expected assertion message'
190+
);
191+
assertionDidFire = true;
192+
}
193+
});
194+
195+
const asyncArrowFn = async () => {};
196+
task(this, asyncArrowFn);
197+
assert.ok(assertionDidFire);
198+
});
170199
});

0 commit comments

Comments
 (0)