Git Product home page Git Product logo

vue-promised's Introduction

vue-promised Build Status npm package coverage 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.

Silver Sponsors

Vue Mastery logo Vuetify logo

Bronze Sponsors

Storyblok logo


Installation

npm install vue-promised
# or
yarn add vue-promised

If you are using Vue 2, you also need to install @vue/composition-api:

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:

<template>
  <div>
    <p v-if="error">Error: {{ error.message }}</p>
    <p v-else-if="isLoading && isDelayElapsed">Loading...</p>
    <ul v-else-if="!isLoading">
      <li v-for="user in data">{{ user.name }}</li>
    </ul>
  </div>
</template>

<script>
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()
  },
}
</script>

๐Ÿ‘‰ Compare this to the version using Vue Promised 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 for breaking changes. v2 exposes the same Promised and a new usePromise function on top of that.

Usage

Composition API

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

<template>
  <Promised :promise="usersPromise">
    <!-- Use the "pending" slot to display a loading message -->
    <template v-slot:pending>
      <p>Loading...</p>
    </template>
    <!-- The default scoped slot will be used as the result -->
    <template v-slot="data">
      <ul>
        <li v-for="user in data">{{ user.name }}</li>
      </ul>
    </template>
    <!-- The "rejected" scoped slot will be used if there is an error -->
    <template v-slot:rejected="error">
      <p>Error: {{ error.message }}</p>
    </template>
  </Promised>
</template>

<script>
export default {
  data: () => ({ usersPromise: null }),

  created() {
    this.usersPromise = this.getUsers()
  },
}
</script>

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:

<Promised :promise="usersPromise">
  <template v-slot:pending="previousData">
    <p>Refreshing</p>
    <ul>
      <li v-for="user in previousData">{{ user.name }}</li>
    </ul>
  </template>
  <template v-slot="data">
    <ul>
      <li v-for="user in data">{{ user.name }}</li>
    </ul>
  </template>
</Promised>

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:

<Promised :promise="promise">
  <template v-slot:combined="{ isPending, isDelayElapsed, data, error }">
    <pre>
      pending: {{ isPending }}
      is delay over: {{ isDelayElapsed }}
      data: {{ data }}
      error: {{ error && error.message }}
    </pre>
  </template>
</Promised>

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:

<Promised :promise="searchResults" :pending-delay="200">
  <template v-slot:combined="{ isPending, isDelayElapsed, data, error }">
    <div>
      <!-- data contains previous data or null when starting -->
      <Search :disabled-pagination="isPending || error" :items="data || []">
        <!-- The Search handles filtering logic with pagination -->
        <template v-slot="{ results, query }">
          <ProfileCard v-for="user in results" :user="user" />
        </template>
        <!--
          Display a loading spinner only if an initial delay of 200ms is elapsed
        -->
        <template v-slot:loading>
          <MySpinner v-if="isPending && isDelayElapsed" />
        </template>
        <!-- `query` is the same as in the default slot -->
        <template v-slot:noResults="{ query }">
          <p v-if="error" class="error">Error: {{ error.message }}</p>
          <p v-else class="info">No results for "{{ query }}"</p>
        </template>
      </Search>
    </div>
  </template>
</Promised>
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:

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):

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:

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.

const { data, error, isPending, isDelayElapsed } = usePromise(fetchUsers())

Signature:

function usePromise<T = unknown>(
  promise: Ref<Promise<T> | null | undefined> | Promise<T> | null | undefined,
  pendingDelay?: Ref<number | string> | number | string
): {
  isPending: Ref<boolean>
  isDelayElapsed: Ref<boolean>
  error: Ref<Error | null | undefined>
  data: Ref<T | null | undefined>
}

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

License

MIT

vue-promised's People

Contributors

anteriovieira avatar atilacamurca avatar bodograumann avatar dependabot-preview[bot] avatar dependabot-support avatar dependabot[bot] avatar edtam avatar gerardrodes avatar gregopet avatar natsuo avatar posva avatar renovate-bot avatar renovate[bot] avatar vladstepanov avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

vue-promised's Issues

Type inference for slot props

What problem is this solving

When using the default slot, the data variable passed into it has type any even when the Promise passed to the Promised component has a defined type.

<template>
  <div>
    <promised :promise="myNumberPromise">
      <template #pending> Loading... </template>
      <template #default="data">
        {{ data }}
      </template>
    </promised>
  </div>
</template>

<script lang="ts">
import { defineComponent } from 'vue'
import { Promised } from 'vue-promised'

export default defineComponent({
  components: {
    Promised,
  },
  setup() {
    const myNumberPromise = new Promise<number>((resolve) => {
      setTimeout(() => {
        resolve(5)
      }, 1000)
    })

    return {
      myNumberPromise,
    }
  },
})
</script>

In an IDE that supports TypeScript you can see that myNumberPromise on line 3 has type Promise<number> as expected, but data on line 5 has type any.

Proposed solution

The variable passed to the default slot should inherit the return type of the Promise passed to the Promised component

Describe alternatives you've considered

The alternative is to just deal with the variable being any typed, since typecasting is not available within the template

[HRM] Cannot create property 'combined' on boolean 'true'"

This happens when when hot module reloading.
Am I missing something?

Code example

<template>
	<Promised
		:promise="invitePromise"
		v-slot:combined="{isPending, isDelayOver, data, error}"
	>
		<div v-loading="isPending">
 			<!-- ... -->
		</div>
	</Promised>
</template>
<script>
export default {
	//...
	created() {
		const inviteRef = db
			.collection('invites')
			.doc(this.$route.params.inviteId)
		this.invitePromise = this.$bind('invite', inviteRef)
	},
}
</script>

Error:
Screen Shot 2019-03-29 at 20 32 38

[Typescript] "No overload matches this call" on [email protected]

Hello,

After recent update on latest typescript's (3.7.2) version I met following error. It happened on node 10.15.3 if it's important. Before update I used typescript (3.6.3) and vue-promised worked like a charm with it. Btw, it works similarly right now except below error.

ะธะทะพะฑั€ะฐะถะตะฝะธะต

Hope this error is encountered not only by me :)

Building with Vue3 + Vite + TS fails

Reproduction

Seems to be a problem with the Shim.

node_modules/vue-promised/dist/vue-promised.d.ts:1:23 - error TS2688: Cannot find type definition file for 'jest'.

1 /// <reference types="jest" />

Steps to reproduce the behavior

1.'install the library on a project that uses Vite'
2. Run 'npm run build'

Expected behavior

Build should not fail

Actual behavior

Build fails

Pass a function returning a Promise instead of a Promise

Hello!

Loving this lib. โค๏ธ

I think it would be very useful to add the possibility to pass a function returning a Promise, instead of a simple promise. This would add the ease of use of throwing before starting a call (checking call prerequisites).

You could wrap the provided function in a new Promise like this:

/**
 * Wrap a function into a Promise.
 * @param {Function} fn Function to wrap
 * @returns {Promise<any>} The function execution wrapped in a Promise
 */
const wrapFn = fn => new Promise((resolve, reject) => fn()
      .then(resolve)
      .catch(reject))

Adding this would make the following possible:

myFunction() {
  if (!this.falsyData) throw new Error('Your data is falsy')
  return apiCall()
}

this.promise = myFunction

The thrown error would then be passed to the <Promised> component rejected slot.

Have a nice day!

ุณู„ุงู… ูƒูŠููƒ

Reproduction

If possible, provide a boiled down editable reproduction using a service like JSFiddle, Codepen or CodeSandbox. A failing unit test is even better! Otherwise provide as much information as possible to reproduce the problem.
If no reproduction is provided and the information is not enough to reproduce the problem, we won't be able to give it a look

Steps to reproduce the behavior

  1. Go to '...'
  2. Click on '....'
  3. Scroll down to '....'
  4. See error

Expected behavior

A clear and concise description of what you expected to happen.

Actual behavior

A clear and concise description of what actually happens.

Additional information

Add any other context about the problem here.

fire resolving event

What problem is this solving

There are some functions I wana to do on my element after the promise has been resolved using Promise component

<template>
<Promised :promise="user">
     <div class="box">Hello {{ user.name }}</div>
</Promised>
</template>
<script>
export default {

    computed: {
        
        user () { return fetch("/users/me") }
    
    },

    mounted () {

        $(".box").style({ color: "red" })

    }

}
</script>

Proposed solution

we can make Promised component fires a resolve event after the promise has been resolved and the slot has been mounted

<template>
<Promised :promise="user" @resolved="color">
     <div class="box">Hello {{ user.name }}</div>
</Promised>
</template>
<script>
export default {

    computed: {
        
        user () { return fetch("/users/me") }
    
    },

    methods: {

        color () {

           $(".box").style({ color: "red" })

        }

    }

}
</script>

README clarifications

I had a couple issues when I first tried to use this that I think could be avoided with some clarified wording in the docs.

The section on the combined slot says:

You can also provide a single combined slot that will receive a context with all relevant information.

Which isn't necessarily clear that you can literally only provide a single combined slot and any others would be ignored. I initially tried to use it alongside the pending slot for debugging purposes. Which brings me to...

pendingDelay is only explained when you get to the API reference section at the bottom. I was confused why the content of the pending slot never appeared even though I could switch to the combined slot and watch the values change. The majority of users aren't going to read the entire README before trying to use something. It would be helpful to mention this in the section that explains how to use the component.

It would also be helpful to note that you need to install @vue/composition-api for this to work with Vue 2.

Next Iteration

After using vue-promised with different clients and in different situations, I came across some issues and I think it's time to work and release a new stable version with some breaking changes.

The main problem I found is not being able to display static content while the promise is pending. And more generally, not being able to have a granular control over how to display things depending on the Promise status. Instead, with current implementation, we are limited to displaying three states.

What I want to do for the next iteration, is to:

  • normalise the slots names (no more multiple possible names)
  • move the multiple promises to a different component as I have never used it in real applications and haven't received feedback about it.
  • Provide a combined slot (not sure about the name) that gives you granular control over the status of the promise

Here's the API I'm leaning towards:

Share your thoughts and feedback:

    <!-- Wait 200ms before showing the pending slot to improve UX -->
    <Promised :promise="promise" :pending-delay="200">
      <MySpinner slot="pending"/>
      <Profile slot-scope="user" :user="user"/>
      <Error slot="rejected" slot-scope="error" :error="error"/>
    </Promised>

    <!-- Displays a wrapping span -->
    <Promised :promise="promise" :pending-delay="200">
      <template slot="combined" slot-scope="{ isPending, isDelayOver, error, data, previousData }">
        <!-- data is always null while pending -->
        <Search :disabled-pagination="isPending || error" :items="data || previousData || []">
          <!-- The Search handles filtering logic with pagination -->
          <template slot-scope="{ results, query }">
            <ProfileCard v-for="user in results" :user="user"/>
          </template>
          <!-- If there is an error, data is null, therefore there are no results and we can display
          the error -->
		  <MySpinner v-if="isPending && isDelayOver" slot="loading"/>
          <template slot="noResults">
            <p v-if="error" class="error">Error: {{ error.message }}</p>
            <p v-else class="info">No results for "{{ query }}"</p>
          </template>
        </Search>
      </template>
    </Promised>

Add a pending delay prop

If a request is fast enough, do not display the pending slot at all. Could default to 300 (ms).
This will improve the perception of speed

how to call the same promise at another time๏ผŸ

  • Feature request: the prop promise's dependencies in other place,just change dependence but the promise would be changed. so i maybe want to get this component's ref like this.refs['promise'].repeat(). in stead of set prop to null then set the same value .

throw error on chrome devtool when use vue-promised

Reproduction

when promise return rejected result
chrome will normal display rejected message but chrome devtool will throw error message

Steps to reproduce the behavior

  1. let promise return rejected result
  2. wait promise change rejected state
  3. chrome devtool throw error message

Expected behavior

chrome devtool should not throw error message

Actual behavior

chrome devtool throw error message

Additional information

vue version๏ผšvue 2.6.14

ๅ›พ็‰‡

ๅ›พ็‰‡

Dependency Dashboard

This issue provides visibility into Renovate updates and their statuses. Learn more

Open

These updates have all been created already. Click a checkbox below to force a retry/rebase of any.

  • chore(deps): update all non-major dependencies (@microsoft/api-extractor, @rollup/plugin-alias, @rollup/plugin-node-resolve, @size-limit/preset-small-lib, @types/jest, @types/jsdom, @vitejs/plugin-vue, @vue/compiler-sfc, @vue/composition-api, @vue/server-renderer, @vue/test-utils, andresz1/size-limit-action, codecov, jest, lint-staged, prettier, prismjs, rollup, size-limit, ts-jest, typescript, vite, vue)
  • chore(deps): update jest monorepo (major) (jest, ts-jest)

  • Check this box to trigger a request for Renovate to run again on this repository

Support for Vue 2.7, no longer require @vue/composition-api

What problem is this solving

Currently, Vue Promised supports Vue 2.6.x or Vue 3. When using 2.6.x, @vue/composition-api is a required dependency, but this is no longer supported in Vue 2.7. Removal of the dependency, as noted by the upgrade guide, causes a warning to be issued in Vue CLI 5:

export 'warn' (imported as 'warn') was not found in 'vue-demi' (possible exports: EffectScope, Vue, Vue2, computed, customRef, defineComponent, del, effectScope, getCurrentInstance, getCurrentScope, h, inject, install, isProxy, isReactive, isReadonly, isRef, isShallow, isVue2, isVue3, markRaw, mergeDefaults, nextTick, onActivated, onBeforeMount, onBeforeUnmount, onBeforeUpdate, onDeactivated, onErrorCaptured, onMounted, onRenderTracked, onRenderTriggered, onScopeDispose, onServerPrefetch, onUnmounted, onUpdated, provide, reactive, readonly, ref, set, shallowReactive, shallowReadonly, shallowRef, toRaw, toRef, toRefs, triggerRef, unref, useAttrs, useCssModule, useCssVars, useSlots, version, watch, watchEffect, watchPostEffect, watchSyncEffect)

I haven't done much digging here, but I assume warn is specific to vue-demi.

Proposed solution

Add support for Vue 2.7 by removing the need for @vue/composition-api.

(Note: I have opened this as a feature request instead of a bug, as it asks for changes targetting a new version of Vue, and not one that is currently supported.)

Allow components for the `tag` option

Right now, it's not possible to pass down props to a custom component. One use case would be using a transition:

      <Promised :promise="promise" tag="transition" name="fade" mode="out-in">
        <template v-slot:pending>
          <p key="loading">Loading</p>
        </template>

        <template v-slot="data">
          <p key="data">{{ data }}</p>
        </template>

        <template v-slot:rejected="error">
          <p key="error">Error: {{ error.message }}</p>
        </template>
      </Promised>

name and mode should be passed down to transition

To get around this, one must use the combined slot instead

Transitions not working

Hi, I would like to combine vue-promised with transitions, so that the loading indicator is propery faded in and faded out, as well as the final result.

<transition name="fade" mode="out-in" appear>
  <Promised :promise="promise">
    <template #pending>
      <p>Loading...</p>
    </template>
     <article>
      Content ...
    </article>
  </Promised>
</transition>

What is expected:
After the promise is resolved, "Loading..." should fade out and the "Content" should fade in.

What is actually happening:
Only "Loading..." is faded in, and will change to the "Content" without transition.

[Typescript] Cannot register component per README

The readme has you:

import { Promised } from 'vue-promised'
Vue.component('Promised', Promised)

But the types are incompatible:

Argument of type 'ComponentOptions<never, Data, DefaultMethods<never>, DefaultComputed, Props, Record<string, any>>' is not assignable to parameter of type 'ComponentOptions<Vue, DefaultData<Vue>, DefaultMethods<Vue>, DefaultComputed, PropsDefinition<Record<string, any>>, Record<string, any>>'.
  Type 'Vue' is not assignable to type 'never'.ts(2345)

Type check failed for prop "promise". Expected Promise, got Promise.

Hello, thank you very much for the very useful project.
The component works as expected, however I keep getting strange error on the console.

[Vue warn]: Invalid prop: type check failed for prop "promise". Expected Promise, got Promise.

I have no idea how this can become a problem since it's exactly what the component expects, right?
I am using Nuxt, if that matters, and here's the code of my view:

<Promised :promise="checkImage(resto.pic)">
  <div slot="pending">Loading...</div>
  <v-card-media slot="then" slot-scope="data" :src="data" height="150px"/>        
  <v-card-media slot="catch" slot-scope="error" :src="error" height="150px"/>
</Promised>

Here's the method which returns the promise:

checkImage(src) {
  return new Promise((resolve, reject) => {		
    let img = new Image()
    img.src = src

    img.onload = () => resolve(src)
    img.onerror = () => reject("/images/resto-placeholder.png")
  })
}

Is there anything wrong on my end? Any help and feedback is greatly appreciated.
Thank you.

Bug: Only first slot element is shown

It seems that when multiple children are passed to the combined slot, only the first one is displayed.

I first thought that maybe it is because it tries to nest my <div>s inside a <span>, but setting v-bind:tag to div also did not help.

<Promised :promise="promise" :tag="div">
  <template #combined="{ isPending, isDelayOver, error }">
    <div>isPending: {{ isPending }}</div>
    <div>isDelayOver: {{ isDelayOver }}</div>
    <div>error: {{ error }}</div>
  </template>
</Promised>

From the code I now see, that you have an assertion:

    assert(
      (Array.isArray(node) && node.length === 1) || node,
      'Provided "combined" scoped-slot cannot be empty and must contain one single children'
    )

So either the || node has to be removed and (if I have not just overlooked it) this information must be added to the documentation; or the possibility to have multiple child nodes in the combined slot should be added.

Pass a callback by props to be called after the promise is resolved

Hey,

I wrote a component similar to this one (I did not know this component already existed) with almost the same spec, but I had an extra props: callBack that I called in the implementation of my Promise component after the promise (passed by props) is resolved.

It's an essential feature I think because almost every time we call a promise we want to call another method after the promise is resolved.

What do you think about this new proposal feature ?
If you want I can develop this feature and do a Pr?

`isPending` is `true` by default

Hey,

I started to use the combined slot as I find it more flexible for forms disabled states.

For example:

<Promised :promise="promise" v-slot:combined="{ isPending, data, error }">
  <button @click="refreshValue" :disabled="isPending">
    Load
  </button>

  <Loader v-if="isPending" />
  <textarea v-else-if="data" v-text="data"></textarea>
  <Alert v-else-if="error" v-text="error" />
</Promised>

By default, isPending is initialized as true when promise is initialized as null. This is frustrating as it makes the form being rendered as in a loading state (at first I was confused as it is not what I expected, and could not find the problem).

data: () => ({
resolved: false,
data: null,
error: null,
isDelayElapsed: false,
}),

if (this.$scopedSlots.combined) {
const node = this.$scopedSlots.combined({
isPending: !this.resolved,
isDelayOver: this.isDelayElapsed,
data: this.data,
error: this.error,
})

The workAround I use is to set a resolved Promise as initialization, but I don't think that is very beautiful ๐Ÿ‘€

data() {
  return {
    promise: Promise.resolve()
  }
}

Maybe a prop could be added to init as false instead of true.

That's not a huge deal though.

Have a nice day!

[BUG] Renders default when the promise throws a null result

<template>
  <promised :promise="confirmPromise">
    <template v-slot:pending="">
      Pending...
    </template>
    <template v-slot="">
      Success
    </template>
    <template v-slot:rejected="">
      Failed
    </template>
  </promised>
</template>

<script>
  import { Promised } from 'vue-promised'

  export default {
    components: {
      Promised,
    },
    computed: {
      confirmPromise () {
        return Promise.reject()
      },
    },
  }
</script>

In this case, the promise rejected with undefined. Rendering "Failed" is expected while rendering "Success" actually.
Fixing this bug is necessary because sometimes there is a fetching returning an error response with no content.

Compatibility with vue 2.6.x

Hi there,

I tried to follow the tutorial of vue-dose to slots in new vue 2.6.x format.
I am very new to slots so likely I get things wrong - but can it be that vue-promised is not yet compatible with vue 2.6.x slots?

Link to the tutorial:
https://vuedose.tips/tips/4

Can't resolve '@vue/composition-api'

I get this error when I use the component, I registered the component globally, I use vue2, does anyone know what I am doing wrong?

`Failed to compile.

./node_modules/vue-demi/lib/v2/index.esm.js
Module not found: Error: Can't resolve '@vue/composition-api' in '\node_modules\vue-demi\lib\v2'`

Provided scoped slot "default" is empty"

<Promised :promise="isProductInCart(size)">
  <template v-slot="productInCart">
    <span
      v-if="productInCart === true"
      class="product__in-cart"
    >
      in cart
    </span>
  </template>
</Promised>

the isProductInCart() method returns a Promise that resolves to Boolean. I'd like to render the "in cart" text conditionally - depending on the returned value. I can't do it because in such a case a "Provided scoped slot "default" is empty" error appears in the console (the code works fine though).

I see 2 workarounds:

  • Render an empty div using v-else
  • Refactor the isProductInCart Promise's code to reject rather than returning false when a product is not in cart.

Both solutions seem hacky.

Is there any reason why you don't allow for empty slots' content? Is it intentional or is it a bug?

Component Events

What problem is this solving

Currently there is no way without using a watcher to invoke a function upon promise resolution or rejection. In the app I'm working on we use notifications when something goes wrong. I can't invoke the notification service from the template.

Proposed solution

Emit @resolved and @rejected events from the promised component when the promise is resolved or rejected.

Example Usage

<promised :promise="makeApiCall()" @rejected="hdlRejected">
   <!-- Some UI Code here -->
</promised

Useful patterns

After #11

I want to add some patterns that I have found useful by showing real examples with comments. Here are some of them:

  • Cancelling promises (automatic)
  • Fetching new data
    • by setting this.promise
    • by using a computed property that returns the promise
  • Using a placeholder
  • Using the combined slot (the Search example)
  • Testing components using Promised

If you have other suggestions or ways you have used vue-promised that could be useful, please share! ๐Ÿ˜„

Reset state when promise becomes null

I feel like currently the behavior regarding :promise="null" is inconsistent.

According to the readme, the properties isPending and isDelayOver of the#combined slot are โ€œreset [โ€ฆ] when the promise prop changesโ€. This is not the case when the promise is changed to null, because the first line in the watcher is if (!promise) return.

My suggestion is to move this guard to after the point where the pending and delay state have been reset.

Basically what I am trying to do is:

<Promised :promise="promise">
  <template #combined="{ error, isPending }">
    <template v-if="isPending">
      <Button @click="promise = doRequest()">Submit</Button>
    </template>
    <template v-else>
      <Button @click="promise = null">Reset</Button>
    </template>
  </template>
</Promise>

But currently the reset does not work like this.

Support vue2.5.x?

Vue-promise is so nice! I like this way! I did not find the vue version information about support, so please ask whether vue 2.5.x is supported๏ผŸ

Global defaults for pending and error

In big applications, it is very common to use similar if not the same pending and error handler. This would prevent a lot of boilerplate from users

Add typing information

It seems vue-promised is currently a javascript-only library. To use it in projects with vue-cli-plugin-typescript, it would be nice to have typing information. My guess is, that the required type file could be quite simple.

Without typing information though, using import { Promised } from "vue-promised"; in a vue-sfc with <script lang="ts"> will give a type-checking error:

Could not find a declaration file for module 'vue-promised'. [โ€ฆ]

Passing an array of promises

Currently, it is possible to pass an array of promises as promises but I'm not sure the approach is flexible enough. Maybe it should be in another component that is more flexible, eg:

  • An empty array displays right now as pending but it could be a specific slot
  • then includes only resolved promises
    • Could there be a second slot with all (also pending) promises
    • get access to them with slot-scope="{ resolved, pending, all }"
    • Always return all and let the user filter
  • A way to reset errors

I'm open to suggestions and usecases

Empty state template when promise returns an empty result

When a new user starts using your service or product, they are often faced with an empty state. They haven't created a new task, product, customer or whatever else your product handles. Or maybe they've just hit that elusive inbox zero point.

A well designed empty state can help your user along, letting them know they can add a task by clicking a button.

The project's code would, in my opinion anyway, look a lot nicer if the empty state code was handled right inside the Promised component. That way you wouldn't need to wrap the contents of the default slot in a v-if.

<Promised :promise="notesPromise">
    <template #pending>
      <p>Loading...</p>
    </template>
    <template #default="data">
      <ul>
        <li v-for="note in data">{{ note.title }}</li>
      </ul>
    </template>
    <template #emptystate>
      <h1>Add your first note</h1>
      <p>Relax and write something beautiful</p>
      <button/> 
    </template>
    <template #rejected="error">
      <p>Error: {{ error.message }}</p>
    </template>
  </Promised>

The template should be visible when the resource has been fetched, but returned no data. The error state takes priority over the empty state. If the empty state template isn't being used, it should instead return the default template.

Run promise programmatically

Instead of executing the promise when it was set, add the ability to execute when we want. Perhaps adding an optional prop and a method.

Something like:

<promised ref="promised" :promise="promise" :immediate="false" />

this.$refs.promised.run()

This will facilitate use cases when a component has one asynchronous method that can be called multiple times.

Failed to mount component: template or render function not defined.

Hello. I am using vue-promised under Nuxt project as a plugin and got this error:

[Vue warn]: Failed to mount component: template or render function not defined.

found in

---> <Promised>
       <Pages/catalog/index.vue> at pages/catalog/index.vue
         <Nuxt>
           <VMain>
             <VApp>
               <Layouts/admin.vue> at layouts/admin.vue
                 <Root>

Plugin:

import Vue from 'vue';
import { Promised } from 'vue-promised';

Vue.component('Promised', Promised);

Nuxt config:

plugins: [
    {src: './plugins/promiseTracker', mode: 'client'},
],

My component:

<Promised :promise="catalog">
        <template v-slot:combined="{ isPending, data, error }">
          {{ data }}
          <app-catalog-list
            :catalog="data"
            @fetchCatalog="fetchCatalog"
            :pending="isPending"
            :error="error"/>
        </template>
</Promised>

Packages I am using:

"nuxt": "^2.15.8",
"@vue/composition-api": "^1.1.1",
"vue-promised": "^2.1.0",

I have tried out another previous versions of vue-promised@^2.0.3, because it works fine in my other project.

But now with older versions am getting an error like:

[Vue warn]: Multiple root nodes returned from render function. Render function should return a single root node.

Expose the return type of usePromise as its own type

What problem is this solving

Because of the limitations of typeof in typescript, it is not possible to easily get the return type of usePromise<T>. Either we can settle for ReturnType<typeof usePromise> which makes the type of data unknown, or there is an ugly workaround using classes. This makes it a pain to do things like pass a usePromise result through props or into a function.

Proposed solution

Export the return type of usePromise as a standalone type, like

type UsePromiseResult<T> = {
  isPending: Ref<boolean>
  isDelayElapsed: Ref<boolean>
  error: Ref<Error | undefined | null>
  data: Ref<T | undefined | null>
}

Describe alternatives you've considered

As I stated above there is a workaround, but it requires creating a new class then creating a new type using that class.

/** Taken from vue-promised */
type Refable<T> = Ref<T> | T
/** necessary because we can't use ReturnType on a generic function and get an accurate type */
class UsePromiseReturnTypeHelper<T> {
  helper = (
    promise: Refable<Promise<T> | null | undefined>,
    pendingDelay?: Refable<string | number> | undefined
  ) => usePromise<T>(promise, pendingDelay)
}
export type UsePromiseReturnType<T> = ReturnType<UsePromiseReturnTypeHelper<T>['helper']>

It's not exactly elegant

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.