Skip to content

Add no-async-in-computed-properties rule #72

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

Merged

Conversation

armano2
Copy link
Contributor

@armano2 armano2 commented Jul 8, 2017

This PR implements rules proposed in #53

DONE:

  • Create documtation
  • Add tests
  • async await
  • new Promise
  • .then .catch .finally
  • Promise.all() Promise.race() Promise.reject() Promise.resolve()
  • setTimeout() setInterval() setImmediate() requestAnimationFrame()

@michalsnik michalsnik self-requested a review July 9, 2017 00:21
@armano2 armano2 force-pushed the patch-2-no-async-in-computed-properties branch 5 times, most recently from 39016d1 to d4c4742 Compare July 13, 2017 23:54
@armano2
Copy link
Contributor Author

armano2 commented Jul 13, 2017

@michalsnik There is one commit there with you as author (i took some of your changes) to align with it 🗡

I cleaned up commits from merges and all tests are passing now 🔢

@armano2 armano2 changed the title No async in computed property functions WIP: No async in computed property functions Jul 14, 2017
@armano2 armano2 changed the title WIP: No async in computed property functions No async in computed property functions Jul 14, 2017
@armano2 armano2 changed the title No async in computed property functions Add no-async-in-computed-properties rule Jul 14, 2017
@michalsnik
Copy link
Member

Great @armano2, thank you so much for all the hard work! I'll review it in details later today, but so far it looks really good! :) I'm glad you figured out more cases, these all make perfect sense to me :)

@armano2 armano2 mentioned this pull request Jul 15, 2017
3 tasks
@armano2 armano2 force-pushed the patch-2-no-async-in-computed-properties branch from f60b332 to 4692050 Compare July 16, 2017 13:35
@@ -0,0 +1,56 @@
# Check if there are no asynchronous action inside computed properties (no-async-in-computed-properties)

Vue.js has a basic construct which lets computed properties collects dependencies and refreshes them thats why its really important to not use any of asynchronous action inside of them.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd go with simple:

Computed properties should be synchronous. Asynchronous actions inside them may not work as expected and can lead to an unexpected behaviour, that's why you should avoid them.
If you need async computed properties you might want to consider using additional plugin https://github.com/foxbenjaminfox/vue-async-computed

export default {
computed: {
pro () {
setTimeout(() => { }, 0)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's put each method causing errors in separate property

@@ -0,0 +1,178 @@
/**
* @fileoverview Check if there are no async inside computed properties.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

asynchronous actions


function isTimedFunction (node) {
return (
node.callee.type === 'Identifier' &&
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add additional node.type === 'CallExpression'. It's good to always check the given node too, and not depend on implementation that is outside this function.

TIMED_FUNCTIONS.indexOf(node.callee.name) !== -1
) || (
node.callee.type === 'MemberExpression' &&
node.callee.object.name === 'window' && (
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a missing check: node.callee.object.type === 'Identifier' &&

node.callee.object.name === 'Promise' &&
PROMISE_METHODS.indexOf(node.callee.property.name) !== -1
) || ( // somePromise.ANYTHING()
node.callee.object && isPromise(node.callee.object)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok this recursion is for checking chained promises, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes

Copy link
Contributor Author

@armano2 armano2 Jul 16, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@michalsnik there can be expression like

el.foo.bar.catch()

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think so @armano2, in your example it would call isPromise with memberExpression -> el.foo.bar which will automatically return false. What you're checking is:

something.crazy.then().test()

But I don't see a real case with this kind of node. I don't think you'll ever end up with custom method after promise method so I think we can even remove this last condition. Add more tests if you want, delete it and check if they all pass.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@michalsnik you are completely right :D
thank you for explanation

if (node.generator) {
forbiddenNodes.push({
node: node,
type: 'yield*'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should call this type simply generator

el.node.loc.end.line <= cp.value.loc.end.line
) {
let message = `Unexpected asynchronous action in "{{name}}" computed property.`
if (el.type === 'await') {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see a lot of repetition here. Please think about creating a map with all types and corresponding names. Then return the message without repeating it :)

create,
meta: {
docs: {
description: 'Check if there are no async inside computed properties.',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

asynchronous actions

`,
parserOptions: { ecmaVersion: 6, sourceType: 'module' },
errors: [{
message: 'Unexpected yield* expression in "foo" computed property.',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

generator function expression would be more correct

@armano2
Copy link
Contributor Author

armano2 commented Jul 16, 2017

Thank you for code review i adapted all requests into code except chained promises

@@ -0,0 +1,64 @@
# Check if there are no asynchronous action inside computed properties (no-async-in-computed-properties)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

actions

const forbiddenNodes = []

const messages = {
promise: 'Unexpected asynchronous action in "{{name}}" computed property.',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking about:

const expressionTypes = {
  promise: 'asynchronous action',
  await: 'await operator',
  yield: 'yield keyword',
  generator: 'generator function expression',
  async: 'async function declaration',
  new: 'Promise object',
  timed: 'timed function'
}

and then you could pass message as:

Unexpected {{expressionName}} in "{{propertyName}}" computed property.

with:

data: {
  propertyName: cp.key,
  expressionName: expressionTypes[cp.key]
}

What do you think? That would prevent you from duplicating the message.

Copy link
Contributor Author

@armano2 armano2 Jul 16, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was not sure if those messages are correct at all. I'm terrible at describing stuff 🥇
if they are going to stay as they are i have no objections

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@michalsnik updated

@michalsnik
Copy link
Member

I put do not merge label on all of your rules. And I'll start merging/finishing reviewing them after #69 is merged, to have a better picture of your changes.

@armano2
Copy link
Contributor Author

armano2 commented Jul 16, 2017

ok

@michalsnik
Copy link
Member

Alright, you can fix conflicts now :)

@armano2 armano2 force-pushed the patch-2-no-async-in-computed-properties branch from 597085f to c0cdedd Compare July 19, 2017 15:58
@armano2
Copy link
Contributor Author

armano2 commented Jul 19, 2017

@michalsnik cleaned up and merged

@mysticatea
Copy link
Member

I'm not sure this rule should disallow yield expressions.
This is not related to async. But it might be dangerous since the value is cached...

export default {
    computed: {
        *numbers() {
            yield 1
            yield 2
            yield 3
        }
    }
}

@armano2
Copy link
Contributor Author

armano2 commented Jul 19, 2017

await nodes are YieldExpression's with babel-eslint < 7.0.0

@mysticatea i don't like this to but babel-eslint < 7.0.0 👎

babel/babel-eslint#350

@mysticatea
Copy link
Member

@armano2 I see. Hmm, how about disallowing async functions instead of await expressions? babel-eslint@6 looks to have node.async === true for async functions correctly. The await expressions on outside of async functions are syntax error.

@armano2
Copy link
Contributor Author

armano2 commented Jul 19, 2017

@mysticatea yes its true and we node.async === true is already supported

Doc: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/yield

The yield keyword causes generator function execution to pause and the value of the expression following the yield keyword is returned to the generator's caller. It can be thought of as a generator-based version of the return keyword.

For me yield can be called async-like.

A yield, which causes the generator to once again pause and return the generator's new value. The next time next() is called, execution resumes with the statement immediately after the yield.

but if you want i can remote it from support and add it as separate rule.

@michalsnik
Copy link
Member

Hmm, I'm ok with both versions but to be honest, who would ever want to set generator as computed property? It doesn't make sense at all. I think we can even remote this check and maybe add another rule for that but personally I don't think it's even necessary.. What do you think guys?

@armano2
Copy link
Contributor Author

armano2 commented Jul 21, 2017

@michalsnik yield/generator removed 🍡

@michalsnik
Copy link
Member

Okay, one last thing @armano2 let's change the await check to async function detection and it's good to go.

@armano2
Copy link
Contributor Author

armano2 commented Jul 22, 2017

@michalsnik but we are checking async function declaration already here: https://github.com/vuejs/eslint-plugin-vue/pull/72/files#diff-ceb2d093cf4ac93cc581e9af530bfebaR70

@armano2 armano2 force-pushed the patch-2-no-async-in-computed-properties branch from 3fafcbb to 67c1086 Compare July 22, 2017 16:46
@michalsnik michalsnik merged commit 5ebdf71 into vuejs:master Jul 23, 2017
@armano2 armano2 deleted the patch-2-no-async-in-computed-properties branch July 23, 2017 13:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants