Giter VIP home page Giter VIP logo

Comments (7)

johanrd avatar johanrd commented on September 27, 2024 1

ok, I guess I could use trackedFunction, but then I'd have to implement the AbortController myself.

request = trackedFunction(this, async () => {
  const token = await getAuth().currentUser?.getIdToken()
  const headers = new Headers({"Authorization": `Bearer ${token}`})
  let response = await fetch(this.args.fetchUrl, {headers});
  let data = await response.json();
  return data;
});

@use latest = keepLatest({
  value: () => this.request.value,
  when: () => this.request.isLoading,
});

An example with a combination of trackedFunction and remoteData could be powerful, I guess.

from limber.

NullVoxPopuli avatar NullVoxPopuli commented on September 27, 2024 1

I love this suggestion!

I think what I want to provide a way to do is something that ultimately looks like this, keeping in mind that classes are not the primary way to use resources, but only one of a few.

const Request = resourceFactory((urlOrUrlFn: string | () => string) => {
  return resource(({ use }) => {
    let tokenRequest = use(TrackedFunction(async () => getAuth()?.currentUser?.getIdToken()));

    if (tokenRequest.isLoading) {
      return tokenRequest; // a State<string>
    }
  
    let headers = new Headers({ Authorization: `Bearer ${tokenRequest.value}` });
    
    let actualRequest = use(RemoteData(urlOrUrlFn, { headers }));
    
    return actualRequest; // State<unknown>, I think
    // the states are different, but I think they both have isLoading and 'value'
    // these should probably be the same State, so you can be certain of all the 
    // promise-handling properties being the same so consumer-facing usage
    // is cleaner
  });
});

export class Demo {
  @use request = Request(() => this.args.fetchUrl));
  
  @use latest = keepLatest({
    value: () => this.request.value,
    when: () => this.request.isLoading,
  });
}

This is some psuedo api as TrackedFunction doesn't exist in a way that is compatible with use yet -- but soon!

from limber.

NullVoxPopuli avatar NullVoxPopuli commented on September 27, 2024

This has been implemented in ember-resources -- idk if you want to write up tutorial text? could be helpful for others!
https://github.com/NullVoxPopuli/ember-resources/blob/main/ember-resources/CHANGELOG.md#640

from limber.

johanrd avatar johanrd commented on September 27, 2024

ok, great, thanks. I will give it a go.

I ended up having a little issue with latest being returned twice, though:

  1. First when the resource is first needed (e.g. from a get())
  2. Then after the actualRequest is returned.

I have some expensive downstream calculations and rendering that takes effect when latest is updated. Is there any good practice to make sure that latest downstream calculations are only triggered on 2), not 1)?

I can think of two solutions:

  1. An ember-concurrency task where both the fetching and the expensive calculations are in a nested task, then updating a @tracked property only at the end to trigger a re-render. (This is in practice an imperative pattern, not reactive, i guess)
  2. Investigate the Formula concept in starbeam.js. Do you have any experience how ready that is? I see there are some mentions of it in the ember-resource docs, but not sure how intentional that is.

Thanks,

from limber.

NullVoxPopuli avatar NullVoxPopuli commented on September 27, 2024

with latest being returned twice, though:

this is as optimized as it can be 🎉
(and found out the two values 💪 )

Is there any good practice to make sure that latest downstream calculations are only triggered on 2), not 1)?

The path here is to handle the non-value case, which you already had to do, but was hidden! (and often forgotten!)

@cached
get expensiveCalculation() {
  if (!this.latest) {
    // or whatever the initial value
    return null; // or {} or [] or whatever is appropriate
  }

  return doSomethingExpensive();
}

This is in practice an imperative pattern, not reactive, i guess

Correct, additionally, ember-concurrency setting properties introduces "hidden states" -- you still have a period of time where your values don't have what you want (until the appropriate thing is awaited), but it's the same situation, just wrapped differently, with implicit states.

Investigate the Formula concept in starbeam.js. Do you have any experience how ready that is?

yeah, it's kind of the same as @cached getters. so @cached is likely what you want

from limber.

johanrd avatar johanrd commented on September 27, 2024

@NullVoxPopuli thanks for your reply.

Yes, I guess checking for !this.latest helps to avoid heavy double renders/calculations upon the first fetch, however caching the getter does not help, since subsequent fetchUrl invalidations rerun the getter every time this.latest is invalidated by fetchUrl. (I have some queryParams are that have {refreshModel: true} (for instance dates), and all queryParams seem to be invalidated when the model refreshes.)

To avoid subsequent double calculations/renders, I feel a need to do track the if @fetchUrl (e.g. tracked from dates in query params) is unequal to the last used @fetchUrl, and only then invalidate data imperatively:

RemoteData component:

@tracked lastFetchedUrl?: string;
@tracked lastData? : any[]

fetchRemoteData = task(async () => {
  const url = this.args.fetchUrl
  if (url === this.lastFetchedUrl) return
  const response = await fetch(url)
  const json = await response.json()
  this.lastFetchedUrl = url
  this.lastData = json 
})

<template>
  {{did-insert (perform this.fetchRemoteData) }}
  {{did-update (perform this.fetchRemoteData) @fetchUrl}}
  {{yield this.lastData this.lastFetchedUrl}}
</template>
}
<RemoteData
  @fetchUrl={{this.fetchUrl}}
  as |data lastFetchedUrl| 
>
  <ExpensiveCalculation
    @data={{data}}
    as |calculatedData|
  />
    <ExpensiveRender
      @data={{calculatedData}}
    />
  <ExpensiveCalculation>
</RemoteData>

This works, but it feels wrong to work against the grain of reactive patterns. But perhaps it has to be done imperatively for full control. ?

from limber.

NullVoxPopuli avatar NullVoxPopuli commented on September 27, 2024

the pattern you describe is specific, and makes sense that you're running in to a bit of a rough edge with some primitives.

"Keeping latest" is what I've called what you're doing: tutorial page | repl

Because you want to only update a value when you have a new value to update with, and ignore all the intermediate stuff.

So, to adapt your example code:

class Demo {
  @use currentRequest = RemoteData(() => this.args.fetchUrl));
  @use data = keepLatest({ 
    value: () => this.request.value,
    when: () => this.request.isLoading,
  });
  
  <template>
    {{yield this.data}}
  </template>
}

I like this pattern because it allows you to still indicate loading or error state while keeping your data displayed

from limber.

Related Issues (20)

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.