{"id":13423961,"url":"https://github.com/foxbenjaminfox/vue-async-computed","last_synced_at":"2025-05-14T02:05:49.309Z","repository":{"id":44641863,"uuid":"55622928","full_name":"foxbenjaminfox/vue-async-computed","owner":"foxbenjaminfox","description":"Async computed properties for Vue.js","archived":false,"fork":false,"pushed_at":"2023-11-19T02:41:42.000Z","size":688,"stargazers_count":1108,"open_issues_count":24,"forks_count":66,"subscribers_count":13,"default_branch":"master","last_synced_at":"2025-05-06T05:02:24.397Z","etag":null,"topics":["async","vue","vue-plugin"],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/foxbenjaminfox.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2016-04-06T16:37:06.000Z","updated_at":"2025-04-30T06:34:27.000Z","dependencies_parsed_at":"2024-01-08T08:57:10.186Z","dependency_job_id":null,"html_url":"https://github.com/foxbenjaminfox/vue-async-computed","commit_stats":{"total_commits":190,"total_committers":21,"mean_commits":9.047619047619047,"dds":"0.16842105263157892","last_synced_commit":"71020d6b4d6c9d0a0e6be6e835ca2bbbe15c3fb0"},"previous_names":[],"tags_count":44,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/foxbenjaminfox%2Fvue-async-computed","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/foxbenjaminfox%2Fvue-async-computed/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/foxbenjaminfox%2Fvue-async-computed/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/foxbenjaminfox%2Fvue-async-computed/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/foxbenjaminfox","download_url":"https://codeload.github.com/foxbenjaminfox/vue-async-computed/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254052692,"owners_count":22006716,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["async","vue","vue-plugin"],"created_at":"2024-07-31T00:00:45.828Z","updated_at":"2025-05-14T02:05:44.294Z","avatar_url":"https://github.com/foxbenjaminfox.png","language":"JavaScript","funding_links":[],"categories":["Awesome Vue.js [![Awesome](https://cdn.rawgit.com/sindresorhus/awesome/d7305f38d29fed78fa85652e3a63e154dd8e8829/media/badge.svg)](https://github.com/sindresorhus/awesome)","JavaScript","Components \u0026 Libraries","公用事业","Utilities [🔝](#readme)","Utilities","Awesome Vue.js"],"sub_categories":["Libraries \u0026 Plugins","Utilities","HTTP请求","HTTP Requests"],"readme":"\u003cbig\u003e\u003ch1 align=\"center\"\u003evue-async-computed\u003c/h1\u003e\u003c/big\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://npmjs.org/package/vue-async-computed\"\u003e\n    \u003cimg src=\"https://img.shields.io/npm/v/vue-async-computed.svg?style=flat-square\"\n         alt=\"NPM Version\"\u003e\n  \u003c/a\u003e\n\n  \u003ca href=\"https://github.com/foxbenjaminfox/vue-async-computed/actions/workflows/ci.yml\"\u003e\n    \u003cimg src=\"https://img.shields.io/github/actions/workflow/status/foxbenjaminfox/vue-async-computed/ci.yml?style=flat-square\"\n         alt=\"Build Status\"\u003e\n  \u003c/a\u003e\n\n  \u003ca href=\"https://npmjs.org/package/vue-async-computed\"\u003e\n    \u003cimg src=\"https://img.shields.io/npm/dm/vue-async-computed.svg?style=flat-square\"\n         alt=\"Downloads\"\u003e\n  \u003c/a\u003e\n\n  \u003ca href=\"https://github.com/foxbenjaminfox/vue-async-computed/blob/master/LICENSE\"\u003e\n    \u003cimg src=\"https://img.shields.io/npm/l/vue-async-computed.svg?style=flat-square\"\n         alt=\"License\"\u003e\n  \u003c/a\u003e\n\u003c/p\u003e\n\nWith this plugin, you can have computed properties in Vue that are computed asynchronously.\n\nWithout using this plugin, you can't do this:\n\n```js\nexport default {\n  data () {\n    return {\n      userId: 1\n    }\n  },\n  computed: {\n    username () {\n      return fetch(`/get-username-by-id/${this.userId}`)\n        // This assumes that this endpoint will send us a response\n        // that contains something like this:\n        // {\n        //   \"username\": \"username-goes-here\"\n        // }\n        .then(response =\u003e response.json())\n        .then(user =\u003e user.username)\n    }\n  }\n}\n```\n\nOr rather, you could, but it wouldn't do what you'd want it to do. But using this plugin, it works just like you'd expect:\n\n```js\nexport default {\n  data () {\n    return {\n      userId: 1\n    }\n  },\n  asyncComputed: {\n    username () {\n      return fetch(`/get-username-by-id/${this.userId}`)\n        .then(r =\u003e r.json())\n        .then(user =\u003e user.username)\n    }\n  }\n}\n```\n\nThis is especially useful with ES7 async functions:\n\n```js\nexport default {\n  asyncComputed: {\n    async someCalculation () {\n      const x = await someAsycFunction()\n      const y = await anotherAsyncFunction()\n      return x + y\n    }\n  }\n}\n```\n\n## Install\n\n```sh\nnpm install --save vue-async-computed\n```\n\nAnd then install `vue-async-computed` via `app.use()` to make it available for all your components:\n\n```js\nimport { createApp } from 'vue'\nimport App from './App.vue'\nimport AsyncComputed from 'vue-async-computed'\n\nconst app = createApp(App)\napp.use(AsyncComputed)\napp.mount('#app')\n```\n\nAlternately, you can link it directly from a CDN:\n\n```html\n\u003cscript src=\"https://unpkg.com/vue@3/dist/vue.global.js\"\u003e\u003c/script\u003e\n\u003cscript src=\"https://unpkg.com/vue-async-computed@4.0.1\"\u003e\u003c/script\u003e\n\n\u003cdiv id=\"app\"\u003e\n  \u003cinput type=\"number\" v-model=\"x\"\u003e + \u003cinput type=\"number\" v-model=\"y\"\u003e\n  = {{sum == null ? 'Loading' : sum}}\n\u003c/div\u003e\n\n\u003cscript\u003e\n  const app = Vue.createApp({\n    data () {\n      return {\n        x: 2,\n        y: 3\n      }\n    },\n    asyncComputed: {\n      async sum () {\n        const total = this.x + this.y\n        await new Promise(resolve =\u003e setTimeout(resolve, 1000))\n        return total\n      }\n    }\n  })\n  app.use(AsyncComputed)\n  app.mount('#app')\n\u003c/script\u003e\n```\n\n## Usage example\n\n```js\nexport default {\n  data () {\n    return {\n      x: 2,\n      y: 3\n    }\n  },\n\n  /*\n    When you create a Vue instance (or component),\n    you can pass an object named \"asyncComputed\" as well as\n    or instead of the standard \"computed\" option. The functions\n    you pass to \"asyncComputed\" should return promises, and the values\n    those promises resolve to are then asynchronously bound to the\n    Vue instance as they resolve. Just as with normal computed\n    properties, if the data the property depends on changes\n    then the property is re-run automatically.\n\n    You can almost completely ignore the fact that behind the\n    scenes they are asynchronous. The one thing to remember is\n    that until a asynchronous property's promise resolves\n    for the first time, the value of the computed property is null.\n  */\n  asyncComputed: {\n    /*\n      Until one second has passed, vm.sum will be null. After that,\n      vm.sum will be 5. If you change vm.x or vm.y, then one\n      second later vm.sum will automatically update itself to be\n      the sum of the values to which you set vm.x and vm.y the previous second.\n    */\n    async sum () {\n      const total = this.x + this.y\n      await new Promise(resolve =\u003e setTimeout(resolve, 1000))\n      return total\n    }\n  }\n}\n```\n\n[Like with regular synchronous computed properties](https://vuejs.org/guide/essentials/computed.html#writable-computed), you can pass an object\nwith a `get` method instead of a function, but unlike regular computed\nproperties, async computed properties are always getter-only. If the\nobject provided has a `set` method it will be ignored.\n\nAsync computed properties can also have a custom default value, which will\nbe used until the data is loaded for the first time:\n\n```js\nexport default {\n  data () {\n    return {\n      postId: 1\n    }\n  },\n  asyncComputed: {\n    blogPostContent: {\n      // The `get` function is the same as the function you would\n      // pass directly as the value to `blogPostContent` if you\n      // didn't need to specify a default value.\n      async get () {\n        const post = await fetch(`/post/${this.postId}`)\n          .then(response =\u003e response.json())\n        return post.postContent\n      },\n       // The computed property `blogPostContent` will have\n       // the value 'Loading...' until the first time the promise\n       // returned from the `get` function resolves.\n       default: 'Loading...'\n    }\n  }\n}\n\n/*\n   Now you can display {{blogPostContent}} in your template, which\n   will show a loading message until the blog post's content arrives\n   from the server.\n*/\n```\n\nYou can instead define the default value as a function, in order to depend on\nprops or on data:\n\n```js\nexport default {\n  data () {\n    return {\n      postId: 1\n    }\n  },\n  asyncComputed: {\n    blogPostContent: {\n      async get () {\n        const post = await fetch(`/post/${this.postId}`)\n          .then(response =\u003e response.json())\n        return post.postContent\n      },\n      default () {\n        return `Loading post ${this.postId}...`\n      }\n    }\n  }\n}\n```\n\nYou can also set a custom global default value in the options passed to `app.use`:\n\n```javascript\napp.use(AsyncComputed, {\n  default: 'Global default value'\n})\n```\n\n## Recalculation\n\nJust like normal computed properties, async computed properties keep track of their dependencies, and are only\nrecalculated if those dependencies change. But often you'll have an async computed property you'll want to run again\nwithout any of its (local) dependencies changing, such as for instance the data may have changed in the database.\n\nYou can set up a `watch` property, listing the additional dependencies to watch.\nYour async computed property will then be recalculated also if any of the watched\ndependencies change, in addition to the real dependencies the property itself has:\n\n```js\nexport default {\n  data () {\n    return {\n      postId: 1,\n      timesPostHasBeenUpdated: 0\n    }\n  },\n  asyncComputed: {\n    // blogPostContent will update its contents if postId is changed\n    // to point to a diffrent post, but will also refetch the post's\n    // contents when you increment timesPostHasBeenUpdated.\n    blogPostContent: {\n      async get () {\n        const post = await fetch(`/post/${this.postId}`)\n          .then(response =\u003e response.json())\n        return post.postContent\n      },\n      watch: ['timesPostHasBeenUpdated']\n    }\n  }\n}\n```\n\nJust like with Vue's normal `watch`, you can use a dotted path in order to watch a nested property. For example, `watch: ['a.b.c', 'd.e']` would declare a dependency on `this.a.b.c` and on `this.d.e`.\n\nYou can trigger re-computation of an async computed property manually, e.g. to re-try if an error occurred during evaluation. This should be avoided if you are able to achieve the same result using a watched property.\n\n````js\nexport default {\n  asyncComputed: {\n    blogPosts: {\n      async get () {\n        return fetch('/posts')\n          .then(response =\u003e response.json())\n      }\n    }\n  },\n  methods: {\n    refresh() {\n      // Triggers an immediate update of blogPosts\n      // Will work even if an update is in progress.\n      this.$asyncComputed.blogPosts.update()\n    }\n  }\n}\n````\n\n### Conditional Recalculation\n\nUsing `watch` it is possible to force the computed property to run again unconditionally.\nIf you need more control over when the computation should be rerun you can use `shouldUpdate`:\n\n```js\n\nexport default {\n  data () {\n    return {\n      postId: 1,\n      // Imagine pageType can be one of 'index', 'details' and 'edit'.\n      pageType: 'index'\n    }\n  },\n  asyncComputed: {\n    blogPostContent: {\n      async get () {\n        const post = await fetch(`/post/${this.postId}`)\n          .then(response =\u003e response.json())\n        return post.postContent\n      },\n      // Will update whenever the pageType or postId changes,\n      // but only if the pageType is not 'index'. This way the\n      // blogPostContent will be refetched only when loading the\n      // 'details' and 'edit' pages.\n      shouldUpdate () {\n        return this.pageType !== 'index'\n      }\n    }\n  }\n}\n```\n\nThe main advantage over adding an `if` statement within the get function is that the old value is still accessible even if the computation is not re-run.\n\n## Lazy properties\n\nNormally, computed properties are both run immediately, and re-run as necessary when their dependencies change.\nWith async computed properties, you sometimes don't want that. With `lazy: true`, an async computed\nproperty will only be computed the first time it's accessed.\n\nFor example:\n```js\nexport default {\n  data () {\n    return {\n      id: 1\n    }\n  },\n  asyncComputed: {\n    mightNotBeNeeded: {\n      lazy: true,\n      async get () {\n        return fetch(`/might-not-be-needed/${this.id}`)\n          .then(response =\u003e response.json())\n          .then(response =\u003e response.value)\n      }\n      // The value of `mightNotBeNeeded` will only be\n      // calculated when it is first accessed.\n    }\n  }\n}\n```\n\n## Computation status\n\nFor each async computed property, an object is added to `$asyncComputed` that contains information about the current computation state of that object. This object contains the following properties:\n\n```js\n{\n  // Can be one of updating, success, error\n  state: 'updating',\n  // A boolean that is true while the property is updating.\n  updating: true,\n  // The property finished updating without errors (the promise was resolved) and the current value is available.\n  success: false,\n  // The promise was rejected.\n  error: false,\n  // The raw error/exception with which the promise was rejected.\n  exception: null\n}\n```\n\nIt is meant to be used in your rendering code to display update / error information:\n\n````html\n\u003cscript\u003e\nexport default {\n  asyncComputed: {\n    async posts() {\n      return fetch('/posts').then(r =\u003e r.json())\n    }\n  }\n}\n\u003c/script\u003e\n\u003ctemplate\u003e\n  \u003c!-- This will display a loading message every time the posts are updated: --\u003e\n  \u003ctemplate v-if=\"$asyncComputed.posts.updating\"\u003eLoading...\u003c/template\u003e\n  \n  \u003c!-- You can display an error message if loading the posts failed. --\u003e\n  \u003ctemplate v-else-if=\"$asyncComputed.posts.error\"\u003e\n    Error while loading posts: {{ $asyncComputed.posts.exception }}\n    \u003cbutton @click=\"$asyncComputed.posts.update()\"\u003eRetry\u003c/button\u003e\n  \u003c/template\u003e\n  \n  \u003c!-- Or finally, display the result: --\u003e\n  \u003ctemplate v-else\u003e\n    {{ posts }}\n  \u003c/template\u003e\n\u003c/template\u003e\n````\n\nNote: If you want to display a special message the first time the posts load, you can use the fact that the default value is null:\n\n```html\n\u003cdiv v-if=\"$asyncComputed.posts.updating \u0026\u0026 posts === null\"\u003e Loading posts \u003c/div\u003e\n```\n\n## Global error handling\n\nBy default, in case of a rejected promise in an async computed property, vue-async-computed will take care of logging the error for you.\n\nIf you want to use a custom logging function, the plugin takes an `errorHandler`\noption, which should be the function you want called with the error information.\nBy default, it will be called with only the error's stack trace as an argument,\nbut if you register the `errorHandler` with `useRawError` set to `true` the\nfunction will receive the raw error, a reference to the `Vue` instance that\nthrew the error and the error's stack trace.\n\nFor example:\n\n```js\napp.use(AsyncComputed, {\n  errorHandler (stack) {\n    console.log('Hey, an error!')\n    console.log('---')\n    console.log(stack)\n  }\n})\n\n// Or with `useRawError`:\napp.use(AsyncComputed, {\n  useRawError: true,\n  errorHandler (err, vm, stack) {\n    console.log('An error occurred!')\n    console.log('The error message was: ' + err.msg)\n    console.log('And the stack trace was:')\n    console.log(stack)\n  }\n})\n```\n\nYou can pass `false` as the `errorHandler` in order to silently ignore rejected promises.\n\n## License\n\nMIT © [Benjamin Fox](https://github.com/foxbenjaminfox)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffoxbenjaminfox%2Fvue-async-computed","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffoxbenjaminfox%2Fvue-async-computed","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffoxbenjaminfox%2Fvue-async-computed/lists"}