Kamil Kiełczewski
Kamil Kiełczewski

Reputation: 92347

Convert Promises to observables

RxJS is very nice library and I want to convert below function from Promises to Observables

    async function userGoods(userName) {
      let user = await userReqest(userName).toPromise();
      let home = await homeReqest(user.id).toPromise();
      let dog = home ? await dogReqest(user.id).toPromise() : null;

      let financeUSD = user.savings;
      if(home) financeUSD += home.price;
      if(dog) financeUSD += dog.price;

      return { 
        USD: financeUSD, 
        EUR: await usd2eurReqest(financeUSD).toPromise() 
      };
    }

Blow I created boiler-plate working (Promises) snippet which allows to easily use rxjs (and can be copied to answer). Snippet below contains 4 api request simulations observables (don't change it)

// Simulated API Request observables
let d = rxjs.operators.delay(100);
let userReqest = (name) => rxjs.of({id:123, name:name, savings: 10000}).pipe(d);
let homeReqest = (userId) => rxjs.of({userId, color:"black", price: 1000000}).pipe(d);
let dogReqest = (userId) => rxjs.of({userId, name:"Pig", price: 1000}).pipe(d);
let usd2eurReqest = (money) => rxjs.of( money*1.1 ).pipe(d);

// How to write below function using obserwables (without Promises) ?
async function userGoods(userName) {
  let user = await userReqest(userName).toPromise();
  let home = await homeReqest(user.id).toPromise();
  let dog = home ? await dogReqest(user.id).toPromise() : null;
  
  let financeUSD = user.savings;
  if(home) financeUSD += home.price;
  if(dog) financeUSD += dog.price;

  return { 
    USD: financeUSD, 
    EUR: await usd2eurReqest(financeUSD).toPromise() 
  };
}


// --------
// TEST
// --------

// you can change this function to support observables instead await
async function run() {
  console.log("John goods", await userGoods("John"));
}

run();
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.5.5/rxjs.umd.min.js" integrity="sha256-85uCh8dPb35sH3WK435rtYUKALbcvEQFC65eg+raeuc=" crossorigin="anonymous"></script>

When I start to convert it myself I write part of nested code but don't know how to finish it - this is what I have so far (probably I do it in wrong-ugly way)

// Simulated API Request observables
let d = rxjs.operators.delay(100);
let userReqest = (name) => rxjs.of({id:123, name:name, savings: 10000}).pipe(d);
let homeReqest = (userId) => rxjs.of({userId, color:"black", price: 1000000}).pipe(d);
let dogReqest = (userId) => rxjs.of({userId, name:"Pig", price: 1000}).pipe(d);
let usd2eurReqest = (money) => rxjs.of( money*1.1 ).pipe(d);

async function userGoods(userName) {
  userReqest(userName).subscribe((user)=> {
    homeReqest(user.id).subscribe((home)=> {
       dogReqest(user.id).subscribe((dog)=> {
         console.log({dog});
         
      })
    })
  });  
  
  // I don't know what to do next here - ?
}


// --------
// TEST
// --------

// you can change this function to support observables instead await
async function run() {
  console.log("John goods", await userGoods("John"));
}

run();
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.5.5/rxjs.umd.min.js" integrity="sha256-85uCh8dPb35sH3WK435rtYUKALbcvEQFC65eg+raeuc=" crossorigin="anonymous"></script>

Upvotes: 0

Views: 313

Answers (2)

frido
frido

Reputation: 14089

Use nested switchMap calls to map to the next Observable while keeping the value of the previous Observable accessible. You can move the code that fetches the dog and maps to the overall user finance to a separate function to reduce some nesting. Then use swichMap to fetch EUR and use a nested map again to emit both { USD, EUR }.

const { of } = rxjs;
const { map, switchMap, delay } = rxjs.operators;

// Simulated API Request observables
const d = 100
let userReqest = (name) => of({id:123, name:name, savings: 10000}).pipe(delay(d));
let homeReqest = (userId) => of({userId, color:"black", price: 1000000}).pipe(delay(d));
let dogReqest = (userId) => of({userId, name:"Pig", price: 1000}).pipe(delay(d));
let usd2eurReqest = (money) => of( money*1.1 ).pipe(delay(d));

function userFinance(user, home) {
  return (home ? dogReqest(user.id) : of(null)).pipe(
    map(dog => user.savings + (home ? home.price : 0) + (dog ? dog.price : 0))
  );
}

function userGoods(userName) {
  return userReqest(userName).pipe(
    switchMap(user => homeReqest(user.id).pipe(
      switchMap(home => userFinance(user, home))
    )),
    switchMap(USD => usd2eurReqest(USD).pipe(
      map(EUR => ({ USD, EUR }))
    ))
  );
}
// --------
// TEST
// --------

function run() {
  userGoods("John").subscribe(
    goods => console.log("John goods", goods)
  );
}

run()
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.5.5/rxjs.umd.min.js" integrity="sha256-85uCh8dPb35sH3WK435rtYUKALbcvEQFC65eg+raeuc=" crossorigin="anonymous"></script>

Upvotes: 1

SergeS
SergeS

Reputation: 11779

When want to combine multiple observables, you should do it rxjs way - ie. using switchMap (https://rxjs-dev.firebaseapp.com/api/operators/switchMap). And then you can use Observable.toPromise(). Subscribtion to observables is way to connect it to "outer" world (Rest of your application)

Then your code in userGoods will look like this (I didn't tested it, but play around with it yourself)

const dog = userReqest(userName).pipe(
  switchMap((user) => homeRequest(user.id).pipe(
    switchMap((home) => (home ? dogRequest(user.id) : null),
  )),
).toPromise();

Upvotes: 0

Related Questions