{"id":13441940,"url":"https://github.com/posva/vue-promised","last_synced_at":"2025-05-13T19:05:57.802Z","repository":{"id":28575068,"uuid":"118746279","full_name":"posva/vue-promised","owner":"posva","description":"💝 Composable Promises \u0026 Promises as components","archived":false,"fork":false,"pushed_at":"2025-04-21T11:39:57.000Z","size":2486,"stargazers_count":1927,"open_issues_count":10,"forks_count":87,"subscribers_count":19,"default_branch":"main","last_synced_at":"2025-04-27T04:39:43.006Z","etag":null,"topics":["component","promise","vue","vue-composition-api"],"latest_commit_sha":null,"homepage":"https://vue-promised.esm.dev/","language":"TypeScript","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/posva.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":".github/CONTRIBUTING.md","funding":".github/funding.yml","license":"LICENSE","code_of_conduct":".github/CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null},"funding":{"github":"posva"}},"created_at":"2018-01-24T09:55:48.000Z","updated_at":"2025-04-24T15:54:17.000Z","dependencies_parsed_at":"2023-11-21T14:30:07.373Z","dependency_job_id":"b5c28602-a0b9-417a-b73f-eb0df9b65c6c","html_url":"https://github.com/posva/vue-promised","commit_stats":{"total_commits":447,"total_committers":20,"mean_commits":22.35,"dds":0.621923937360179,"last_synced_commit":"e103aedc58a3fcd93640fe7c50239b8997a7c906"},"previous_names":["posva/vue-promise-blocks"],"tags_count":20,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/posva%2Fvue-promised","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/posva%2Fvue-promised/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/posva%2Fvue-promised/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/posva%2Fvue-promised/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/posva","download_url":"https://codeload.github.com/posva/vue-promised/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":251089405,"owners_count":21534511,"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":["component","promise","vue","vue-composition-api"],"created_at":"2024-07-31T03:01:39.889Z","updated_at":"2025-04-27T04:39:49.727Z","avatar_url":"https://github.com/posva.png","language":"TypeScript","funding_links":["https://github.com/sponsors/posva"],"categories":["HarmonyOS","JavaScript","Packages","TypeScript"],"sub_categories":["Windows Manager"],"readme":"# vue-promised [![test](https://github.com/posva/vue-promised/actions/workflows/test.yml/badge.svg)](https://github.com/posva/vue-promised/actions/workflows/test.yml) [![npm package](https://badgen.net/npm/v/vue-promised)](https://www.npmjs.com/package/vue-promised) [![codecov](https://codecov.io/gh/posva/vue-promised/graph/badge.svg?token=moy6zDeJRi)](https://codecov.io/gh/posva/vue-promised) [![thanks](https://badgen.net/badge/thanks/%E2%99%A5/ff69b4)](https://github.com/posva/thanks)\n\n\u003e Handle your promises with style 🎀\n\n**Help me keep working on Open Source in a sustainable way 🚀**. Help me with as little as \\$1 a month, [sponsor me on Github](https://github.com/sponsors/posva).\n\n\u003ch3 align=\"center\"\u003eSilver Sponsors\u003c/h3\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://www.vuemastery.com\" title=\"Vue Mastery\" target=\"_blank\"\u003e\n    \u003cimg src=\"https://www.vuemastery.com/images/vuemastery.svg\" alt=\"Vue Mastery logo\" height=\"48px\"\u003e\n  \u003c/a\u003e\n\n  \u003ca href=\"https://vuetifyjs.com\" target=\"_blank\" title=\"Vuetify\"\u003e\n    \u003cimg src=\"https://cdn.vuetifyjs.com/docs/images/logos/vuetify-logo-light-text.svg\" alt=\"Vuetify logo\" height=\"48px\"\u003e\n  \u003c/a\u003e\n\u003c/p\u003e\n\n\u003ch3 align=\"center\"\u003eBronze Sponsors\u003c/h3\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://www.storyblok.com\" target=\"_blank\" title=\"Storyblok\"\u003e\n    \u003cimg src=\"https://a.storyblok.com/f/51376/3856x824/fea44d52a9/colored-full.png\" alt=\"Storyblok logo\" height=\"32px\"\u003e\n  \u003c/a\u003e\n\u003c/p\u003e\n\n---\n\n## Installation\n\n```bash\nnpm install vue-promised\n# or\nyarn add vue-promised\n```\n\nIf you are using Vue 2, you also need to install `@vue/composition-api`:\n\n```bash\nyarn add @vue/composition-api\n```\n\n## Motivation\n\nWhen dealing with asynchronous requests like fetching content through API calls, you may want to display the loading state with a spinner, handle the error and even hide everything until at least 200ms have been elapsed so the user doesn't see a loading spinner flashing when the request takes very little time. This is quite some boilerplate, and you need to repeat this for every request you want:\n\n```vue\n\u003ctemplate\u003e\n  \u003cdiv\u003e\n    \u003cp v-if=\"error\"\u003eError: {{ error.message }}\u003c/p\u003e\n    \u003cp v-else-if=\"isLoading \u0026\u0026 isDelayElapsed\"\u003eLoading...\u003c/p\u003e\n    \u003cul v-else-if=\"!isLoading\"\u003e\n      \u003cli v-for=\"user in data\"\u003e{{ user.name }}\u003c/li\u003e\n    \u003c/ul\u003e\n  \u003c/div\u003e\n\u003c/template\u003e\n\n\u003cscript\u003e\nexport default {\n  data: () =\u003e ({\n    isLoading: false,\n    error: null,\n    data: null,\n    isDelayElapsed: false,\n  }),\n\n  methods: {\n    fetchUsers() {\n      this.error = null\n      this.isLoading = true\n      this.isDelayElapsed = false\n      getUsers()\n        .then((users) =\u003e {\n          this.data = users\n        })\n        .catch((error) =\u003e {\n          this.error = error\n        })\n        .finally(() =\u003e {\n          this.isLoading = false\n        })\n      setTimeout(() =\u003e {\n        this.isDelayElapsed = true\n      }, 200)\n    },\n  },\n\n  created() {\n    this.fetchUsers()\n  },\n}\n\u003c/script\u003e\n```\n\n👉 Compare this to [the version using Vue Promised](#using-pending-default-and-rejected-slots) that handles new promises.\n\nThat is quite a lot of boilerplate and it's not handling cancelling on going requests when `fetchUsers` is called again. Vue Promised encapsulates all of that to reduce the boilerplate.\n\n## Migrating from `v1`\n\nCheck the [Changelog](https://github.com/posva/vue-promised/blob/v2/CHANGELOG.md#200-2020-11-16) for breaking changes. v2 exposes the same `Promised` and a new `usePromise` function on top of that.\n\n## Usage\n\n### Composition API\n\n```js\nimport { Promised, usePromise } from 'vue-promised'\n\nVue.component('Promised', Promised)\nexport default {\n  setup() {\n    const usersPromise = ref(fetchUsers())\n    const promised = usePromise(usersPromise)\n\n    return {\n      ...promised,\n      // spreads the following properties:\n      // data, isPending, isDelayElapsed, error\n    }\n  },\n}\n```\n\n### Component\n\nVue Promised also exposes the same API via a component named `Promised`.\nIn the following examples, `promise` is a Promise but can initially be `null`. `data` contains the result of the promise. You can of course name it the way you want:\n\n#### Using `pending`, `default` and `rejected` slots\n\n```vue\n\u003ctemplate\u003e\n  \u003cPromised :promise=\"usersPromise\"\u003e\n    \u003c!-- Use the \"pending\" slot to display a loading message --\u003e\n    \u003ctemplate v-slot:pending\u003e\n      \u003cp\u003eLoading...\u003c/p\u003e\n    \u003c/template\u003e\n    \u003c!-- The default scoped slot will be used as the result --\u003e\n    \u003ctemplate v-slot=\"data\"\u003e\n      \u003cul\u003e\n        \u003cli v-for=\"user in data\"\u003e{{ user.name }}\u003c/li\u003e\n      \u003c/ul\u003e\n    \u003c/template\u003e\n    \u003c!-- The \"rejected\" scoped slot will be used if there is an error --\u003e\n    \u003ctemplate v-slot:rejected=\"error\"\u003e\n      \u003cp\u003eError: {{ error.message }}\u003c/p\u003e\n    \u003c/template\u003e\n  \u003c/Promised\u003e\n\u003c/template\u003e\n\n\u003cscript\u003e\nexport default {\n  data: () =\u003e ({ usersPromise: null }),\n\n  created() {\n    this.usersPromise = this.getUsers()\n  },\n}\n\u003c/script\u003e\n```\n\nNote the `pending` slot will by default, display after a 200ms delay. This is a reasonable default to avoid layout shifts when API calls are fast enough. The perceived speed is also higher. You can customize it with the `pendingDelay` prop.\n\nThe `pending` slot can also receive the data that was previously available:\n\n```vue\n\u003cPromised :promise=\"usersPromise\"\u003e\n  \u003ctemplate v-slot:pending=\"previousData\"\u003e\n    \u003cp\u003eRefreshing\u003c/p\u003e\n    \u003cul\u003e\n      \u003cli v-for=\"user in previousData\"\u003e{{ user.name }}\u003c/li\u003e\n    \u003c/ul\u003e\n  \u003c/template\u003e\n  \u003ctemplate v-slot=\"data\"\u003e\n    \u003cul\u003e\n      \u003cli v-for=\"user in data\"\u003e{{ user.name }}\u003c/li\u003e\n    \u003c/ul\u003e\n  \u003c/template\u003e\n\u003c/Promised\u003e\n```\n\nAlthough, depending on the use case, this could create duplication and using a `combined` slot would be a better approach.\n\n#### Using one single `combined` slot\n\nSometimes, you need to customize **how** things are displayed rather than **what** is displayed. Disabling a search input, displaying an overlaying spinner, etc. Instead of using multiple slots, you can provide one single `combined` slot that will receive a context with all relevant information. That way you can customize the props of a component, toggle content with your own `v-if` but still benefit from a declarative approach:\n\n```vue\n\u003cPromised :promise=\"promise\"\u003e\n  \u003ctemplate v-slot:combined=\"{ isPending, isDelayElapsed, data, error }\"\u003e\n    \u003cpre\u003e\n      pending: {{ isPending }}\n      is delay over: {{ isDelayElapsed }}\n      data: {{ data }}\n      error: {{ error \u0026\u0026 error.message }}\n    \u003c/pre\u003e\n  \u003c/template\u003e\n\u003c/Promised\u003e\n```\n\nThis allows to create more advanced async templates like this one featuring a Search component that must be displayed while the `searchResults` are being fetched:\n\n```vue\n\u003cPromised :promise=\"searchResults\" :pending-delay=\"200\"\u003e\n  \u003ctemplate v-slot:combined=\"{ isPending, isDelayElapsed, data, error }\"\u003e\n    \u003cdiv\u003e\n      \u003c!-- data contains previous data or null when starting --\u003e\n      \u003cSearch :disabled-pagination=\"isPending || error\" :items=\"data || []\"\u003e\n        \u003c!-- The Search handles filtering logic with pagination --\u003e\n        \u003ctemplate v-slot=\"{ results, query }\"\u003e\n          \u003cProfileCard v-for=\"user in results\" :user=\"user\" /\u003e\n        \u003c/template\u003e\n        \u003c!--\n          Display a loading spinner only if an initial delay of 200ms is elapsed\n        --\u003e\n        \u003ctemplate v-slot:loading\u003e\n          \u003cMySpinner v-if=\"isPending \u0026\u0026 isDelayElapsed\" /\u003e\n        \u003c/template\u003e\n        \u003c!-- `query` is the same as in the default slot --\u003e\n        \u003ctemplate v-slot:noResults=\"{ query }\"\u003e\n          \u003cp v-if=\"error\" class=\"error\"\u003eError: {{ error.message }}\u003c/p\u003e\n          \u003cp v-else class=\"info\"\u003eNo results for \"{{ query }}\"\u003c/p\u003e\n        \u003c/template\u003e\n      \u003c/Search\u003e\n    \u003c/div\u003e\n  \u003c/template\u003e\n\u003c/Promised\u003e\n```\n\n##### `context` object\n\n- `isPending`: is `true` while the promise is in a _pending_ status. Becomes `false` once the promise is resolved **or** rejected. It is reset to `true` when the `promise` prop changes.\n- `isRejected` is `false`. Becomes `true` once the promise is _rejected_. It is reset to `false` when the `promise` prop changes.\n- `isResolved` is `false`. Becomes `true` once the promise is _resolved_. It is reset to `false` when the `promise` prop changes.\n- `isDelayElapsed`: is `true` once the `pendingDelay` is over or if `pendingDelay` is 0. Becomes `false` after the specified delay (200 by default). It is reset when the `promise` prop changes.\n- `data`: contains the last resolved value from `promise`. This means it will contain the previous succesfully (non cancelled) result.\n- `error`: contains last rejection or `null` if the promise was fullfiled.\n\n### Setting the `promise`\n\nThere are different ways to provide a promise to `Promised`. The first one, is setting it in the created hook:\n\n```js\nexport default {\n  data: () =\u003e ({ promise: null }),\n  created() {\n    this.promise = fetchData()\n  },\n}\n```\n\nBut most of the time, you can use a computed property. This makes even more sense if you are passing a prop or a data property to the function returning a promise (`fetchData` in the example):\n\n```js\nexport default {\n  props: ['id'],\n  computed: {\n    promise() {\n      return fetchData(this.id)\n    },\n  },\n}\n```\n\nYou can also set the `promise` prop to `null` to reset the Promised component to the initial state: no error, no data, and pending:\n\n```js\nexport default {\n  data: () =\u003e ({ promise: null }),\n  methods: {\n    resetPromise() {\n      this.promise = null\n    },\n  },\n}\n```\n\n## API Reference\n\n### `usePromise`\n\n`usePromise` returns an object of `Ref` representing the state of the promise.\n\n```ts\nconst { data, error, isPending, isDelayElapsed } = usePromise(fetchUsers())\n```\n\nSignature:\n\n```ts\nfunction usePromise\u003cT = unknown\u003e(\n  promise: Ref\u003cPromise\u003cT\u003e | null | undefined\u003e | Promise\u003cT\u003e | null | undefined,\n  pendingDelay?: Ref\u003cnumber | string\u003e | number | string\n): {\n  isPending: Ref\u003cboolean\u003e\n  isDelayElapsed: Ref\u003cboolean\u003e\n  error: Ref\u003cError | null | undefined\u003e\n  data: Ref\u003cT | null | undefined\u003e\n}\n```\n\n### `Promised` component\n\n`Promised` will watch its prop `promise` and change its state accordingly.\n\n#### props\n\n| Name           | Description                                                               | Type      |\n| -------------- | ------------------------------------------------------------------------- | --------- |\n| `promise`      | Promise to be resolved                                                    | `Promise` |\n| `pendingDelay` | Delay in ms to wait before displaying the pending slot. Defaults to `200` | `Number \\| String` |\n\n#### slots\n\nAll slots but `combined` can be used as _scoped_ or regular slots.\n\n| Name       | Description                                                                     | Scope                                     |\n| ---------- | ------------------------------------------------------------------------------- | ----------------------------------------- |\n| `pending`  | Content to display while the promise is pending and before pendingDelay is over | `previousData`: previously resolved value |\n| _default_  | Content to display once the promise has been successfully resolved              | `data`: resolved value                    |\n| `rejected` | Content to display if the promise is rejected                                   | `error`: rejection reason                 |\n| `combined` | Combines all slots to provide a granular control over what should be displayed  | `context` [See details](#context-object)  |\n\n## License\n\n[MIT](http://opensource.org/licenses/MIT)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fposva%2Fvue-promised","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fposva%2Fvue-promised","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fposva%2Fvue-promised/lists"}