Home > Mobile >  How to make reactivity work with a Promise started from a constructor?
How to make reactivity work with a Promise started from a constructor?

Time:07-15

I have a class Thing, whose constructor starts an asynchronous fetch operation. When the fetch completes, the result is assigned to a field on the Thing object:

  class Thing {
    constructor() {
      this.image = null
      this.load()
    }

    async load() {
      const response = await fetch('https://placekitten.com/200/300')
      const blob = await response.blob()
      this.image = await createImageBitmap(blob)
    }
  }

I'm using thing.image in a Vue component. The problem is that Vue doesn't pick up on the change in image when the Promise resolves. I think I understand why this happens: in the constructor, this refers to the raw Thing, and not Vue's reactive proxy wrapper. So the assignment to this.image ends up bypassing the proxy.

It works if I move the load call out of the constructor, so that this inside the load function refers to the reactive proxy. But that makes my Thing class harder to use.

Is there a better way to handle this issue?

Minimal example (Vue playground link):

<script setup>
  import { reactive } from 'vue'

  class Thing {
    constructor() {
      this.image = null
      this.load() // This does not trigger reactivity.
    }

    async load() {
      const response = await fetch('https://placekitten.com/200/300')
      const blob = await response.blob()
      this.image = await createImageBitmap(blob)
    }
  }

  const thing = reactive(new Thing())
  // thing.load() // This triggers reactivity as expected.
</script>

<template>
  <p v-if="thing.image">
    Image size is {{thing.image.width}}×{{thing.image.height}}
  </p>
  <p v-if="!thing.image">
    Loading...
  </p>
</template>

CodePudding user response:

define your class attribute as a ref

<script setup>
  import { reactive, ref } from 'vue'

  class Thing {
    constructor() {
      this.image = ref(null)
      this.load()
    }

    async load() {
      const response = await fetch('https://placekitten.com/200/300')
      const blob = await response.blob()
      this.image.value = await createImageBitmap(blob)
    }
  }

  const thing = reactive(new Thing())
  // thing.load() // This triggers reactivity as expected.
</script>

<template>
  <p v-if="thing.image">
    Image size is {{thing.image.width}}×{{thing.image.height}}
  </p>
  <p v-if="!thing.image">
    Loading...
  </p>
</template>
  • Related