Skip to content

Idea: built-in support for promises #1370

@mweststrate

Description

@mweststrate

This proposal aims to making working with promises easier.

Some ideas:

  1. Expose observable.promise(Promise) than returns { state, value, promise }. In other words, fromPromise of the mobx-utils package
  2. observable(Promise) and @observable x = Promise will automatically convert to a promise observable (e.g. call observable.promise)
    3 Allow @computed to return Promises, and if so, convert this again in a promise structure.

Example code:

class User {
    @observable name

    @computed
    get profile() {
      // return a promise
      return fetch(`/endpoint/${name}`)
    }

    @computed
    get profileImageUrl() {
       // pluck the image from the profile promise
       return this.profile.state === "fullfilled"
          ? this.profile.value.image
          : "/defaultavatar.png"
    }

    @computed({ initialValue: [] })
    get tags() {
       // chain another promise onto the profile promise
       return this.profile.then(() =>
          fetch(`/endpoint/${name}/tags`)
       })
    }

    @computed
    get isLoading() {
       // are we fetching data?
       return this.profile.state === "pending" || this.tags.state === "pending"
    }

    @computed
    get isLoadingTakingLong() {
      // provide spinner if loading takes long!
      return Promise.race([
         new Promise(resolve => setTimeout(() => resolve(true), 1000),
         new Promise(resolve => this.isLoading.then((done) => { if (done) resolve(false) })
      ])
    }
}

const UserView = observer(({ user }) => (
   <div>
       <h1>{user.name}<h1/>
       { user.isLoadingTakingLong.value && <Spinner /> }
       <img src={user.profileImageUrl} />
       { user.tags.value.join(" - ") }
   </div>
))

I checked, this will even work correct with debug tools like trace.

Problems

The problem with the decorators is that their return type signature is incorrect; as the type Promise<T> would be converted into Promise<T> & { state: "pending" | "fullfilled" | "error", value: T | undefined | error }

Possible solutions:

@computed
 get profile() {
      // upcast
      return fetch(`/endpoint/${name}`) as IObservablePromise
}

@computed
get profile() {
      // wrapper fn (needing a better name)
      return observablify(`/endpoint/${name}`) 
}

// ... but still:
@computed
 get profile() {
      // upcast
      const p fetch(`/endpoint/${name}`) as IObservablePromise
      p.state // undefined!
      return p
}

Caveat: only the first part of a computed will be tracked. But that is explainable and makes it somewhere easy as well.

cc @spion @urugator @danielearwicker

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions