Skip to content

Commit

Permalink
feat: add when (vueuse#143)
Browse files Browse the repository at this point in the history
  • Loading branch information
antfu committed Oct 10, 2020
1 parent 761373a commit 81e5218
Show file tree
Hide file tree
Showing 4 changed files with 151 additions and 0 deletions.
16 changes: 16 additions & 0 deletions packages/core/utils/index.ts
Expand Up @@ -43,3 +43,19 @@ export function tryOnUnmounted(fn: () => void) {
if (getCurrentInstance())
onUnmounted(fn)
}

export function promiseSleep(ms: number): Promise<void> {
return new Promise((resolve) => {
setTimeout(resolve, ms)
})
}

export function promiseTimeout(ms: number): Promise<void> {
return new Promise((resolve, reject) => {
setTimeout(reject, ms)
})
}

export function invoke<T>(fn: () => T): T {
return fn()
}
38 changes: 38 additions & 0 deletions packages/core/when/index.md
@@ -0,0 +1,38 @@
# when

> Await ref/reactive changes, a.k.a one time async condition watch.
## Usage

#### Wait for some async data to be ready

```js
import { when, useAsyncState } from '@vueuse/core'

const { state, ready } = useAsyncState(
fetch('https://jsonplaceholder.typicode.com/todos/1').then(t => t.json()),
{},
)

;(async() => {
await when(ready).toBe(true)

console.log(state) // state is now ready!
})()
```

#### Wait for custom conditions

> You can use `invoke` to call the async function.
```js
import { when, usCounter, invoke } from '@vueuse/core'

const { count } = usCounter()

invoke(async() => {
await when(count).toMatch(v => v > 7)

alert('Counter is now larger than 7!')
})
```
41 changes: 41 additions & 0 deletions packages/core/when/index.stories.tsx
@@ -0,0 +1,41 @@
import { defineDemo, html } from '../../_docs'
import { defineComponent, ref } from 'vue-demi'
import { when } from '.'
import { invoke } from '../utils'
import { useCounter } from '../useCounter'

defineDemo(
{
name: 'when',
category: 'Utilities',
docs: require('./index.md'),
module,
},
// Vue component for the demo section
defineComponent({
setup() {
const el = ref<HTMLElement>(null)
const counter = useCounter()

invoke(async() => {
await when(counter.count).toBe(7)

alert('You got 7!')
})

return {
...counter,
el,
}
},

template: html`
<div ref='el'>
<note>Add to 7 to show the alert.</note>
<p>Count: {{count}}</p>
<button @click="inc()">Increment</button>
<button @click="dec()">Decrement</button>
</div>
`,
}),
)
56 changes: 56 additions & 0 deletions packages/core/when/index.ts
@@ -0,0 +1,56 @@
import { Ref, WatchOptions, watch } from 'vue-demi'
import { promiseTimeout } from '../utils'

interface WhenToMatchOptions {
flush?: WatchOptions['flush']
timeout?: number
}

export function when<T>(r: Ref<T>) {
function toMatch(
condition: (v: T) => boolean,
{ flush = 'post', timeout }: WhenToMatchOptions = {},
): Promise<void> {
let stop: Function | null = null
const watcher = new Promise<void>((resolve) => {
stop = watch(r, (v) => {
if (condition(v)) {
stop?.()
resolve()
}
}, {
flush,
immediate: true,
})
})

const promises = [watcher]
if (timeout) {
promises.push(
promiseTimeout(timeout)
.finally(() => { stop?.() }),
)
}

return Promise.any(promises)
}

function toBe(value: T, options?: WhenToMatchOptions) {
return toMatch(v => v === value, options)
}

function toBeTruthy(options?: WhenToMatchOptions) {
return toMatch(v => Boolean(v), options)
}

function toNotNull(options?: WhenToMatchOptions) {
return toMatch(v => v != null, options)
}

return {
toMatch,
toBe,
toBeTruthy,
toNotNull,
}
}

0 comments on commit 81e5218

Please sign in to comment.