Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/posva/vue-promised
💝 Composable Promises & Promises as components
https://github.com/posva/vue-promised
component promise vue vue-composition-api
Last synced: 20 days ago
JSON representation
💝 Composable Promises & Promises as components
- Host: GitHub
- URL: https://github.com/posva/vue-promised
- Owner: posva
- License: mit
- Created: 2018-01-24T09:55:48.000Z (almost 7 years ago)
- Default Branch: main
- Last Pushed: 2024-04-11T18:40:43.000Z (7 months ago)
- Last Synced: 2024-04-14T10:15:35.530Z (7 months ago)
- Topics: component, promise, vue, vue-composition-api
- Language: TypeScript
- Homepage: https://vue-promised.esm.dev/
- Size: 2.28 MB
- Stars: 1,880
- Watchers: 21
- Forks: 87
- Open Issues: 10
-
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
Awesome Lists containing this project
- stars - posva/vue-promised
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)
> 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
Bronze Sponsors
---
## 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
Error: {{ error.message }}
Loading...
- {{ user.name }}
export default {
data: () => ({
isLoading: false,
error: null,
data: null,
isDelayElapsed: false,
}),methods: {
fetchUsers() {
this.error = null
this.isLoading = true
this.isDelayElapsed = false
getUsers()
.then((users) => {
this.data = users
})
.catch((error) => {
this.error = error
})
.finally(() => {
this.isLoading = false
})
setTimeout(() => {
this.isDelayElapsed = true
}, 200)
},
},created() {
this.fetchUsers()
},
}```
👉 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
Loading...
- {{ user.name }}
Error: {{ error.message }}
export default {
data: () => ({ usersPromise: null }),
created() {
this.usersPromise = this.getUsers()
},
}
```
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
Refreshing
- {{ user.name }}
- {{ user.name }}
```
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
pending: {{ isPending }}
is delay over: {{ isDelayElapsed }}
data: {{ data }}
error: {{ error && error.message }}
```
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
Error: {{ error.message }}
No results for "{{ query }}"
```
##### `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)