openlang

Accessing a resolved result outside its promise

I'm working on my auth system (yep, the one in my earlier post), and I've ended up tangled in Promise.t chains (pretty typical for javascript).

Here's what I've got currently:

 fetch(...)
 ->Promise.then(Response.json)
 ->Promise.thenResolve(payload => {
   let record = JSON.Decode.object(payload)->Option.getOrThrow
   // let profile = Profile.fromJson(record)

   Console.log(record)
   // TODO: I should set the model to SignedInWithProfile(profile)
 })

However, that SignedInWithProfile variant needs to reach the enclosing scope rather than stay inside the promise. Since this is a reducer, react tricks aren't available to me, can I do something to solve this?

Could I write some javascript binding to get around it? I'm a little confused about this.

The conversation continues 3

Follow the reasoning
  1. Permalink

    Simplest approach: Use await directly.

    It works at the top level as well as within an async function's body.

    Documentation: Async / Await | ReScript Language Manual (https://rescript-lang.org/docs/manual/async-await)

     let resolved: Profile.t = await fetch("")
     ->Promise.then(Response.json)
     ->Promise.thenResolve(payload => {
       let record = JSON.Decode.object(payload)->Option.getOrThrow
       Profile.fromJson(record)
     })

    In general though, it's good practice to trigger reducer state updates through a dispatch function:

     type model = {
       profile: option<Profile.t>,
     }
    
     type event = SetProfile(Profile.t)
    
     let reduceState = (model, event) => {
       switch event {
       | SetProfile(profile) => {
           ...model,
           profile: Some(profile),
         }
       }
     }
    
     @react.component
     let make = () => {
       let (model, sendAction) = React.useReducer(
         reduceState,
         {
           profile: None,
         },
       )
    
       React.useEffect(() => {
         fetch("")
         ->Promise.then(Response.json)
         ->Promise.thenResolve(payload => {
           let record = JSON.Decode.object(payload)->Option.getOrThrow
           let profile = Profile.fromJson(record)
    
           sendAction(SetProfile(profile))
         })
         ->Promise.ignore
    
         None
       }, [sendAction])
    
       switch model.profile {
       | Some(profile) => <div> {React.string("Profile ready")} </div>
       | None => <div> {React.string("Waiting...")} </div>
       }
     }
  2. Permalink

    I'm inside a reducer, so there's no async context where I can use await.

    Dispatching within a promise is somewhat more involved in my case. Maybe I ought to make everything async by default and put promises throughout my code, dunno.

    I'm pretty confused about structuring my code in the first place. I likely just need some more experience doing this.

  3. Permalink

    Generally speaking, you ought to avoid starting side effects within a reducer. That's why reducers are normally free of async functions, and ought to stay like that.

    When an action changes reducer state, you can make that state a dependency for something like useEffect, and perform your (async) side effects there, including fetch calls.

    Listing your component's possible states explicitly can also help:

     type model = Pending | Ready(Profile.t)
    
     type event = ReplaceState(model)
    
     let reduceState = (model, event) =>
       switch event {
       | ReplaceState(model) => model
       }
    
     @react.component
     let make = () => {
       let (model, sendAction) = React.useReducer(reduceState, Pending)
    
       React.useEffect(() => {
         fetch("")
         ->Promise.then(Response.json)
         ->Promise.thenResolve(payload => {
           let record = JSON.Decode.object(payload)->Option.getOrThrow
           let profile = Profile.fromJson(record)
    
           sendAction(ReplaceState(Ready(profile)))
         })
         ->Promise.ignore
    
         None
       }, [sendAction]
    
       switch model {
       | Ready(profile) => <div> {React.string("Profile ready")} </div>
       | Pending => <div> {React.string("Waiting...")} </div>
       }
     }

    Having said that, I frequently reach for a trick that forces an async context whenever needed:

     module AsyncRunner = {
       let run = (createTask: unit => promise<unit>) =>
         createTask()->Promise.ignore
     }
    
     let handleSync = () =>
       AsyncRunner.run(async () => {
         let payload = await fetch("")->Promise.then(Response.json)
         let record = JSON.Decode.object(payload)->Option.getOrThrow
         let profile = Profile.fromJson(record)
    
         sendAction(ReplaceState(Ready(profile)))
       })

Other sides of the same question

  1. Making the sender appear as a label instead of an email address using Java Mail

    Im sending messages through the Java Mail API. On arrival, the inbox displays the address that sent the message. Id like some…

    Replies 2Latest activity java
  2. Dynamics CRM access from TypeScript

    Because my CRM code runs in a webresource, I have to write to retrieve the entity's data. Regrettably, that expression fails…

    Replies 2Latest activity typescript
  3. What date formatting does a Gleam HTTP response header need?

    I want to send a Date header. The required format looks like this: This is my current attempt:

    Replies 3Latest activity gleam