# vue-promised [![Build Status](https://badgen.net/circleci/github/posva/vue-promised/v2)](https://circleci.com/gh/posva/vue-promised) [![npm package](https://badgen.net/npm/v/vue-promised/v2)](https://www.npmjs.com/package/vue-promised) [![coverage](https://badgen.net/codecov/c/github/posva/vue-promised/v2)](https://codecov.io/github/posva/vue-promised) [![thanks](https://badgen.net/badge/thanks/%E2%99%A5/ff69b4)](https://github.com/posva/thanks) > Handle your promises with style 🎀 **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).

Silver Sponsors

Vue Mastery logo Vuetify logo

Bronze Sponsors

Storyblok logo

--- ## Installation ```bash npm install vue-promised # or yarn add vue-promised ``` If you are using Vue 2, you also need to install `@vue/composition-api`: ```bash yarn add @vue/composition-api ``` ## Motivation When 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: ```vue ``` 👉 Compare this to [the version using Vue Promised](#using-pending-default-and-rejected-slots) that handles new promises. That 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. ## Migrating from `v1` Check 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. ## Usage ### Composition API ```js import { Promised, usePromise } from 'vue-promised' Vue.component('Promised', Promised) export default { setup() { const usersPromise = ref(fetchUsers()) const promised = usePromise(usersPromise) return { ...promised, // spreads the following properties: // data, isPending, isDelayElapsed, error } }, } ``` ### Component Vue Promised also exposes the same API via a component named `Promised`. In 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: #### Using `pending`, `default` and `rejected` slots ```vue ``` Note 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. The `pending` slot can also receive the data that was previously available: ```vue ``` Although, depending on the use case, this could create duplication and using a `combined` slot would be a better approach. #### Using one single `combined` slot Sometimes, 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: ```vue ``` This allows to create more advanced async templates like this one featuring a Search component that must be displayed while the `searchResults` are being fetched: ```vue ``` ##### `context` object - `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. - `isRejected` is `false`. Becomes `true` once the promise is _rejected_. It is reset to `false` when the `promise` prop changes. - `isResolved` is `false`. Becomes `true` once the promise is _resolved_. It is reset to `false` when the `promise` prop changes. - `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. - `data`: contains the last resolved value from `promise`. This means it will contain the previous succesfully (non cancelled) result. - `error`: contains last rejection or `null` if the promise was fullfiled. ### Setting the `promise` There are different ways to provide a promise to `Promised`. The first one, is setting it in the created hook: ```js export default { data: () => ({ promise: null }), created() { this.promise = fetchData() }, } ``` But 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): ```js export default { props: ['id'], computed: { promise() { return fetchData(this.id) }, }, } ``` You can also set the `promise` prop to `null` to reset the Promised component to the initial state: no error, no data, and pending: ```js export default { data: () => ({ promise: null }), methods: { resetPromise() { this.promise = null }, }, } ``` ## API Reference ### `usePromise` `usePromise` returns an object of `Ref` representing the state of the promise. ```ts const { data, error, isPending, isDelayElapsed } = usePromise(fetchUsers()) ``` Signature: ```ts function usePromise( promise: Ref | null | undefined> | Promise | null | undefined, pendingDelay?: Ref | number | string ): { isPending: Ref isDelayElapsed: Ref error: Ref data: Ref } ``` ### `Promised` component `Promised` will watch its prop `promise` and change its state accordingly. #### props | Name | Description | Type | | -------------- | ------------------------------------------------------------------------- | --------- | | `promise` | Promise to be resolved | `Promise` | | `pendingDelay` | Delay in ms to wait before displaying the pending slot. Defaults to `200` | `Number \| String` | #### slots All slots but `combined` can be used as _scoped_ or regular slots. | Name | Description | Scope | | ---------- | ------------------------------------------------------------------------------- | ----------------------------------------- | | `pending` | Content to display while the promise is pending and before pendingDelay is over | `previousData`: previously resolved value | | _default_ | Content to display once the promise has been successfully resolved | `data`: resolved value | | `rejected` | Content to display if the promise is rejected | `error`: rejection reason | | `combined` | Combines all slots to provide a granular control over what should be displayed | `context` [See details](#context-object) | ## License [MIT](http://opensource.org/licenses/MIT)