-
Notifications
You must be signed in to change notification settings - Fork 779
Docs: Recommend async functions over assert.async() #1811
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
In the simple example from the documentation, the timeout problem doesn’t actually occur (if fetchDouble() synchronously throws an Error, QUnit reports a test failure despite the assert.async() still being outstanding), but in more complicated tests it can still happen. We’ve recently migrated some tests in MediaWiki core and extensions to async+await syntax – see [1], [2], [3]. [1]: https://gerrit.wikimedia.org/r/c/mediawiki/extensions/VisualEditor/+/1026171 [2]: https://gerrit.wikimedia.org/r/c/mediawiki/core/+/1106934 [3]: https://gerrit.wikimedia.org/r/c/mediawiki/extensions/Wikibase/+/1188501
The committers listed above are authorized under a signed CLA. |
|
Thanks @lucaswerkmeister, this is long over-due to recommend in the docs! The advice is good. But I think we need a better examples to demonstrate why. I want to avoid making a bold claim that seems easy to contradict for a well-meaning person with contrary experience. In the linked examples, most do not trade timeouts for fast failures. In the common case of Promise-like or Node.js-style result callbacks, this doesn't happen easily. When there is an easy translation from assert.async() to an async function, then the assert.async() code was likely already safe from timeouts: function fetchDouble(input, callback) {
if (Math.random() < 0.5) {
callback(null, input * 2);
} else {
callback('Error');
}
QUnit.test('async example', function (assert) {
const done = assert.async();
fetchDouble(21, function (err, result) {
assert.strictEqual(err, null);
assert.strictEqual(result, 42);
done();
});
});Use of Another example of The other thing I found in those MediaWiki examples, is the risk of false positives: QUnit.test('async example', function (assert) {
const done = assert.async();
fetch('/api/status')
.then(function (resp) {
return resp.json();
}).
.then(function (data) {
assert.true(data.isSuccess); // Bad example: May not be reached!
})
.finally(done);
});This is yet another example of assertions in application-controlled code, which can simply not execute at all, and allow the test to pass. This problem is avoided if the assertion is placed outside the Promise-chain, or if the chain is returned the QUnit.test, or rewritten with async-await such that the assertions are in the test function (this is the same under the hood as a Promise chain returned to QUnit.test because an async function returns a Promise). QUnit.test('async example', function (assert) {
return fetch('/api/status')
.then(function (resp) {
return resp.json();
}).
.then(function (data) {
assert.true(data.isSuccess); // OK. If the chain fails earlier, QUnit knows.
});
});QUnit.test('async example', async function (assert) {
const statusData = null;
await fetch('/api/status')
.then(function (resp) {
return resp.json();
}).
.then(function (data) {
statusData = data;
});
assert.true(statusData.isSuccess); // OK. Assertion is always reached.
});QUnit.test('async example', async function (assert) {
const resp = await fetch('/api/status');
const data = await resp.json();
assert.true(data.isSuccess); // OK. If the chain fails earlier, QUnit knows.
});Perhaps one of these risks is worth calling out. Can you think of a concise example where we'd trade timeouts for fast failures? |
I don’t think this is actually a false positive? If the promise fails, then it’s an unhandled promise rejection ( You could probably still have a false positive if you’re using jQuery Deferreds instead of native Promises – they’re fairly compatible, but I don’t think they run the same unhandled rejection handlers. I’ll think a bit more about it later :) |
In the simple example from the documentation, the timeout problem doesn’t actually occur (if
fetchDouble()synchronously throws an Error, QUnit reports a test failure despite theassert.async()still being outstanding), but in more complicated tests it can still happen. We’ve recently migrated some tests in MediaWiki core and extensions to async+await syntax – see 1, 2, 3.