{"action":"opened","issue":{"url":"https://api.github.com/repos/vuejs/vuex/issues/1248","repository_url":"https://api.github.com/repos/vuejs/vuex","labels_url":"https://api.github.com/repos/vuejs/vuex/issues/1248/labels{/name}","comments_url":"https://api.github.com/repos/vuejs/vuex/issues/1248/comments","events_url":"https://api.github.com/repos/vuejs/vuex/issues/1248/events","html_url":"https://github.com/vuejs/vuex/issues/1248","id":316911432,"number":1248,"title":"Vuex.Store.watch with immediate:true is too immediate","user":{"login":"suau","id":2596700,"avatar_url":"https://avatars0.githubusercontent.com/u/2596700?v=4","gravatar_id":"","url":"https://api.github.com/users/suau","html_url":"https://github.com/suau","followers_url":"https://api.github.com/users/suau/followers","following_url":"https://api.github.com/users/suau/following{/other_user}","gists_url":"https://api.github.com/users/suau/gists{/gist_id}","starred_url":"https://api.github.com/users/suau/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/suau/subscriptions","organizations_url":"https://api.github.com/users/suau/orgs","repos_url":"https://api.github.com/users/suau/repos","events_url":"https://api.github.com/users/suau/events{/privacy}","received_events_url":"https://api.github.com/users/suau/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":0,"created_at":"2018-04-23T17:37:11Z","updated_at":"2018-04-23T17:37:11Z","closed_at":null,"author_association":"NONE","body":"### Expected:\r\nThe `Vuex.Store.watch` method returns a `unwatch function`, which can be called to stop watching.\r\nThis can be called from within the callback as expected. (note: the callback will only be called on changes, the initial value will only be returned if the immediate option is used)\r\n\r\n### Issue:\r\nThe issue arises when `Vuex.Store.watch` is used with the `immediate option`, which will trigger the callback immediately with the current value of the expression.\r\nUsing `immediate: true` it isn't possible to unwatch from within the callback anymore as the callback is called before the watch() method returns the unwatch method.\r\n\r\n### Why is this important/usecase:\r\nGet a store object until an expected value is returned, then stop watching and execute an action.\r\nExample 1: Get the \"user\" object from the store, let's assume:\r\n- undefined: the user hasn't been loaded from localstorage yet \r\n- null: means there is no user\r\n- object: the user is loaded and logged in\r\n\r\nhere is some code on how I'd do that (not working, due to mentioned behavior/bug):\r\n```\r\nfunction getUser(store: Store): Promise<any> {\r\n    return new Promise((resolve, reject) => {\r\n        const unwatch = store.watch(\r\n            (state: any) => {\r\n                return state.user;\r\n            },\r\n            (value: any) => {\r\n                if (value || value === null) {\r\n                    unwatch(); // THIS WILL THROW AN ERROR IF IT IS THE FIRST VALUE RECEIVED\r\n                    resolve(value)\r\n                }\r\n            },\r\n            {\r\n                immediate: true\r\n            }\r\n        );\r\n    });\r\n}\r\n```\r\n\r\nI've created a fiddle here to showcase the issue, (just set immediate: false to see how it should work):\r\nhttps://jsfiddle.net/suaujs/8b9m301c/\r\n\r\n### Possible solutions\r\n- Vuex.Store.watch should never run the getter or callback synchronously in the `watch()` call\r\n- pass in an unwatch function as the third parameter into the callback (see workaround wrapper below)\r\n- make use of the callbacks return value, e.g. `return true` will stop watching\r\n\r\n### Workaround\r\nManually check if the current state is already meeting your `unwatch()` expectations and only register a store watcher if that's not the case. Fix for Example 1:\r\n```\r\nfunction getUser(store: Store): Promise<any> {\r\n    return new Promise((resolve, reject) => {\r\n        const user = store.state.user;\r\n        if (user || user === null) {\r\n            resolve(user);\r\n        } else {\r\n            const unwatch = store.watch(\r\n                (state: any) => {\r\n                    return state.user;\r\n                },\r\n                (value: any) => {\r\n                    if (value || value === null) {\r\n                        unwatch();\r\n                        resolve(value)\r\n                    }\r\n                }\r\n            );\r\n        }\r\n    });\r\n}\r\n```\r\n### Workaround wrapper\r\nThis is a more general purpose wrapper as a workaround (not fully tested)\r\n```\r\nfunction wrapper(store: Store<any>, getter: (state?: any, getters?: any) => any,\r\n                  callback: (newValue?: any, oldValue?: any, unwatch?: () => void) => void, options?: any): () => void {\r\n    // callback receives a unwatch function as third parameter\r\n    if (options.immediate) {\r\n        let shouldStop = false;\r\n        const current = getter(store.state, store.getters);\r\n        callback(current, undefined, () => {\r\n            shouldStop = true;\r\n        });\r\n        if (shouldStop) {\r\n            return () => {};\r\n        }\r\n    }\r\n    const unwatch = store.watch(\r\n        getter,\r\n        (newValue: any, oldValue: any) => {\r\n            callback(newValue, oldValue, unwatch);\r\n        },\r\n        Object.assign({}, options, {immediate: false}));\r\n    return unwatch;\r\n}\r\n```\r\n"}}
{"actor":{"display_login":"suau"}}