From 81e5218f985bc61b9a0e40cd5f5aa60b892bd788 Mon Sep 17 00:00:00 2001 From: Anthony Fu Date: Sat, 10 Oct 2020 23:16:16 +0800 Subject: [PATCH] feat: add when (#143) --- packages/core/utils/index.ts | 16 ++++++++ packages/core/when/index.md | 38 +++++++++++++++++++ packages/core/when/index.stories.tsx | 41 ++++++++++++++++++++ packages/core/when/index.ts | 56 ++++++++++++++++++++++++++++ 4 files changed, 151 insertions(+) create mode 100644 packages/core/when/index.md create mode 100644 packages/core/when/index.stories.tsx create mode 100644 packages/core/when/index.ts diff --git a/packages/core/utils/index.ts b/packages/core/utils/index.ts index 347de488e04..e20656ee987 100644 --- a/packages/core/utils/index.ts +++ b/packages/core/utils/index.ts @@ -43,3 +43,19 @@ export function tryOnUnmounted(fn: () => void) { if (getCurrentInstance()) onUnmounted(fn) } + +export function promiseSleep(ms: number): Promise { + return new Promise((resolve) => { + setTimeout(resolve, ms) + }) +} + +export function promiseTimeout(ms: number): Promise { + return new Promise((resolve, reject) => { + setTimeout(reject, ms) + }) +} + +export function invoke(fn: () => T): T { + return fn() +} diff --git a/packages/core/when/index.md b/packages/core/when/index.md new file mode 100644 index 00000000000..161792dd12c --- /dev/null +++ b/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!') +}) +``` diff --git a/packages/core/when/index.stories.tsx b/packages/core/when/index.stories.tsx new file mode 100644 index 00000000000..58e7ac8b087 --- /dev/null +++ b/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(null) + const counter = useCounter() + + invoke(async() => { + await when(counter.count).toBe(7) + + alert('You got 7!') + }) + + return { + ...counter, + el, + } + }, + + template: html` +
+ Add to 7 to show the alert. +

Count: {{count}}

+ + +
+ `, + }), +) diff --git a/packages/core/when/index.ts b/packages/core/when/index.ts new file mode 100644 index 00000000000..874e15ccd7d --- /dev/null +++ b/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(r: Ref) { + function toMatch( + condition: (v: T) => boolean, + { flush = 'post', timeout }: WhenToMatchOptions = {}, + ): Promise { + let stop: Function | null = null + const watcher = new Promise((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, + } +}