rslint_core/groups/errors/no_await_in_loop.rs
1use crate::rule_prelude::*;
2use SyntaxKind::*;
3
4declare_lint! {
5 /**
6 Disallow await inside of loops.
7
8 You may want to `await` a promise until it is fulfilled or rejected, inside of loops. In such cases, to take
9 full advantage of concurrency, you should __not__ `await` the promise in every iteration, otherwise your async
10 operations will be executed serially.
11 Generally it is recommended that you create all promises, then use `Promise.all` for them. This way your async
12 operations will be performed concurrently.
13
14 ## Incorrect Code Exapmles
15
16 ```js
17 async function foo(xs) {
18 const results = [];
19 for (const x of xs) {
20 // iteration does not proceed until `bar(x)` completes
21 results.push(await bar(x));
22 }
23 return baz(results);
24 }
25 ```
26
27 ## Correct Code Examples
28
29 ```js
30 async function foo(xs) {
31 const results = [];
32 for (const x of xs) {
33 // push a promise to the array; it does not prevent the iteration
34 results.push(bar(x));
35 }
36 // we wait for all the promises concurrently
37 return baz(await Promise.all(results));
38 }
39 ```
40 */
41 #[derive(Default)]
42 NoAwaitInLoop,
43 errors,
44 tags(Recommended),
45 "no-await-in-loop"
46}
47
48#[typetag::serde]
49impl CstRule for NoAwaitInLoop {
50 fn check_node(&self, node: &SyntaxNode, ctx: &mut RuleCtx) -> Option<()> {
51 if let Some(err_node) = node.children().find(|node| node.kind() == AWAIT_EXPR) {
52 for ancestor in node.ancestors() {
53 match ancestor.kind() {
54 FN_DECL | FN_EXPR | ARROW_EXPR => return None,
55 FOR_OF_STMT if ancestor.to::<ast::ForOfStmt>().await_token().is_some() => {
56 return None
57 }
58 _ => {}
59 }
60
61 if ancestor.is_loop()
62 && ancestor
63 .child_with_ast::<ast::Stmt>()?
64 .range()
65 .contains_range(node.text_range())
66 {
67 let err = ctx.err(self.name(), "Unexpected `await` in loop")
68 .primary(err_node, "this expression causes the loop to wait for the promise to resolve before continuing")
69 .footer_note("the promises are resolved one after the other, not at the same time")
70 .footer_help(format!("try adding the promises to an array, then resolving them all outside the loop using `{}`", color("Promise.all(/* promises */)")));
71
72 ctx.add_err(err);
73 return None;
74 }
75 }
76 }
77 None
78 }
79}
80
81rule_tests! {
82 NoAwaitInLoop::default(),
83 err: {
84 "
85 async function foo() {
86 const res = [];
87 for(var i = 1; i < 20; i++) {
88 res.push(await i);
89 }
90 }
91 ",
92 "
93 async () => {
94 while(true) {
95 await i;
96 }
97 }
98 "
99 },
100 ok: {
101 "
102 for (let i of await foo) {}
103 "
104 }
105}