Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Enhancement: [await-thenable] should check that for-await loop is used on an async iterable #8858

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

Closed
4 tasks done
kirkwaiblinger opened this issue Apr 5, 2024 · 7 comments · Fixed by #10008
Closed
4 tasks done
Labels
accepting prs Go ahead, send a pull request that resolves this issue enhancement New feature or request locked due to age Please open a new issue if you'd like to say more. See https://typescript-eslint.io/contributing. package: eslint-plugin Issues related to @typescript-eslint/eslint-plugin

Comments

@kirkwaiblinger
Copy link
Member

Before You File a Bug Report Please Confirm You Have Done The Following...

  • I have tried restarting my IDE and the issue persists.
  • I have updated to the latest version of the packages.
  • I have searched for related issues and found none that matched my issue.
  • I have read the FAQ and my problem is not listed.

Playground Link

https://typescript-eslint.io/play/#ts=5.4.3&showAST=es&fileType=.tsx&code=GYewTgBAhg7lCWAXCAKAxiAdgZ2diIwEA2gOSYikA0EpiAFgKaZQBGANo9tbU2I6QC6ASggBvAL4AoRgA8ADuGRiIEoA&eslintrc=N4KABGBEBOCuA2BTAzpAXGUEKQAIBcBPABxQGNoBLY-AWhXkoDt8B6AQwHd3K78ALRE3YAjJOiiJo0APbRI4MAF8QSoA&tsconfig=N4KABGBEDGD2C2AHAlgGwKYCcDyiAuysAdgM6QBcYoEEkJemy0eAcgK6qoDCAFutAGsylBm3TgwAXxCSgA&tokens=false

Repro Code

for await (const s of ['no', 'thenables', 'here']) {}
export { }

ESLint Config

module.exports = {
  "rules": {
    "@typescript-eslint/await-thenable": "error"
  }
}

tsconfig

{
  "compilerOptions": {
    "strictNullChecks": true
  }
}

Expected Result

I expect that the first line should flag due to for await being used on something that does not have a Symbol.asyncIterator.

Actual Result

No error.

Additional Info

Note that it's unambiguous IMO that the example given should flag. However, I, personally, would also like for the rule to flag on misused for-await-of, where the iterable is a synchronous iterator over promises. That should be written as an ordinary for loop. It's only a true async iterable that should be allowed to be iterated over in a for-await-of loop.

Refresher on for-await-of: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for-await...of

@kirkwaiblinger kirkwaiblinger added bug Something isn't working package: eslint-plugin Issues related to @typescript-eslint/eslint-plugin triage Waiting for team members to take a look labels Apr 5, 2024
@Josh-Cena
Copy link
Member

This sounds a lot more like the promise aggregators rule (#8094) where you also want to check "some" or "all" of them are thenable... Which is why again I'm unhappy with that being a separate rule, because people would naturally expect await-thenable to work on things that get implicitly awaited too!

@kirkwaiblinger
Copy link
Member Author

@Josh-Cena +1

@kirkwaiblinger
Copy link
Member Author

@Josh-Cena

Note that there is also another completely redundant check to prohibit awaiting thenables in return-await, so two different rules flag this code for the exact same reason.

return-await:

const type = checker.getTypeAtLocation(child);
const isThenable = tsutils.isThenableType(checker, expression, type);
if (!isAwait && !isThenable) {
return;
}
if (isAwait && !isThenable) {
// any/unknown could be thenable; do not auto-fix
const useAutoFix = !(isTypeAnyType(type) || isTypeUnknownType(type));
const fix = (fixer: TSESLint.RuleFixer): TSESLint.RuleFix | null =>
removeAwait(fixer, node);
context.report({
messageId: 'nonPromiseAwait',
node,
...(useAutoFix
? { fix }
: {
suggest: [
{
messageId: 'nonPromiseAwait',
fix,
},
],
}),
});
return;
}

await-thenable:

AwaitExpression(node): void {
const type = services.getTypeAtLocation(node.argument);
if (isTypeAnyType(type) || isTypeUnknownType(type)) {
return;
}
const originalNode = services.esTreeNodeToTSNodeMap.get(node);
if (!tsutils.isThenableType(checker, originalNode.expression, type)) {
context.report({
messageId: 'await',
node,
suggest: [
{
messageId: 'removeAwait',
fix(fixer): TSESLint.RuleFix {
const awaitKeyword = nullThrows(
context.sourceCode.getFirstToken(node, isAwaitKeyword),
NullThrowsReasons.MissingToken('await', 'await expression'),
);
return fixer.remove(awaitKeyword);
},
},
],
});
}
},

I guess that's a separate issue though.

@JoshuaKGoldberg JoshuaKGoldberg added accepting prs Go ahead, send a pull request that resolves this issue and removed triage Waiting for team members to take a look labels Jun 3, 2024
@Josh-Cena Josh-Cena changed the title Bug: [await-thenable] should check for-await loop Enhancement: [await-thenable] should check for-await loop Jun 5, 2024
@Josh-Cena Josh-Cena added enhancement: plugin rule option New rule option for an existing eslint-plugin rule enhancement New feature or request and removed bug Something isn't working enhancement: plugin rule option New rule option for an existing eslint-plugin rule labels Jun 5, 2024
@Josh-Cena
Copy link
Member

Josh-Cena commented Jun 5, 2024

Just realized this request isn't about whether the loop is over AsyncIterable<T> where T is a promise or not, but whether it's over Iterable<any> or AsyncIterable<any> 🤦‍♂️ Even if you have AsyncIterable<number>, you can't use something other than for await to unroll it, so we can't report that.

However, note that if you have an Iterable<Promise<number>>, you might want to use for await so the elements are awaited. I think this should still be banned because there are some undesirable aspects:

We can not add an option unless people complain loudly about it. We should add the reasoning above to the docs.

@Josh-Cena Josh-Cena changed the title Enhancement: [await-thenable] should check for-await loop Enhancement: [await-thenable] should check that for-await loop is used on an async iterable Jun 5, 2024
@Josh-Cena
Copy link
Member

Slightly awkward is the rule naming. We are not checking for thenables here (and even renaming it to await-promises wouldn't help). I'm wondering if we should rename this rule to no-unnecessary-await, or just put this in another rule.

@kirkwaiblinger
Copy link
Member Author

Just realized this request isn't about whether the loop is over AsyncIterable<T> where T is a promise or not, but whether it's over Iterable<any> or AsyncIterable<any> 🤦‍♂️ Even if you have AsyncIterable<number>, you can't use something other than for await to unroll it, so we can't report that.

Ah, yeah, I didn't even think about that implication of the example. Meant to signal that the array was not AsyncIterable, but I can see how that admits the Iterable<Promise<T>> interpretation as well.

However, note that if you have an Iterable<Promise<number>>, you might want to use for await so the elements are awaited. I think this should still be banned

Yep, wholeheartedly agree!

@kirkwaiblinger
Copy link
Member Author

kirkwaiblinger commented Jun 5, 2024

Slightly awkward is the rule naming. We are not checking for thenables here (and even renaming it to await-promises wouldn't help). I'm wondering if we should rename this rule to no-unnecessary-await, or just put this in another rule.

Well, yes, and it is a quite bad name even for the current usage, and I think that's worthy of a separate discussion.

As it is, the rule really is dont-await-non-thenables. I'd argue that an AsyncIterable is something whose [Symbol.asyncIterator]() returns an object with a .next() method that is intended/required to return a Thenable, and that's what we want to require as the iteree of a for await loop. So the notion of "awaiting thenables" is contained in that IMO.

@github-actions github-actions bot added the locked due to age Please open a new issue if you'd like to say more. See https://typescript-eslint.io/contributing. label Oct 9, 2024
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Oct 9, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
accepting prs Go ahead, send a pull request that resolves this issue enhancement New feature or request locked due to age Please open a new issue if you'd like to say more. See https://typescript-eslint.io/contributing. package: eslint-plugin Issues related to @typescript-eslint/eslint-plugin
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants