Multiple ajax requests in a model


#1

I’m using ember-ajax to pull some data from an external API in my model. I need data from one end point (/personalprofile) to be used in two other calls (/attorney, /cpa). However, I only want to call the personalProfile once, since it has the contact Id for both the attorney and the CPA. But, I can’t for the life of my figure out how to do both in one model. Is there a way a noob like me can reasonably combine the attorney and CPA objects in one model? For now I’m just using it like I have below, but I know I have repeated code and I feel like I’m making an unnecessary 2nd call to the /personalProfile endpoint.

 model (params) {
    return RSVP.hash ({

      CPA: this.get ('ajax')
        .post ('/personalProfile', {
          data: {contactId: params.contact_id}
        }) 
// Gets the CPA's Name
        .then (profileResponse => {
          return this.get ('ajax')
          .post ('/CPA', {
            data: { CPAId: profileResponse.ProfessionalContact_CPA }
          });
        }),
      
      attorney: this.get ('ajax')
      .post ('/personalProfile', {
        data: {contactId: params.contact_id}
      })
//Gets the Attorney's name
      .then (profileResponse => { 
        return this.get ('ajax')
        .post ('/attorney', {
          data: { attorneyId: profileResponse.ProfessionalContact_Attorney }
        });
      })
    });
  },

#2

To have chained Promises you must wrap them in another Promise.

model() {
  return new Promise((resolve, reject) => {
    outerPromise.then((outerPromiseResolved) => {
      innerPromise.then((innerPromiseResolved) => {
         resolve(innerPromiseResolved);
       });
    });
  });
}

#3

Something like the below should work. You don’t need to to declare promises and chain them since the then will do that automatically.

model (params) {
  return this.get ('ajax')
    .post ('/personalProfile', {
      data: {contactId: params.contact_id}
    }) 
    .then(
      profileResponse => rsvp.hash({
        // Gets the CPA's Name
        CPA: this.get ('ajax').post ('/CPA', {
          data: { CPAId: profileResponse.ProfessionalContact_CPA }
        }),
        //Gets the Attorney's name
        attorney: this.get ('ajax').post ('/attorney', {
          data: { attorneyId: profileResponse.ProfessionalContact_Attorney }
        })
      });
    );

    // model will be an object containing 2 items, the CPA's Name 
    // in the first index and the Attorney's name in the second. 
    // If you need the profileResponse data as well, just add it to 
    // the hash and it'll pass through as well

#4

Depends of what your model hook should return (and resolve with). You model hook returns the ajax request to /personProfile and resolves with profileResponse. I don’t think that’s what you expect if you read the code. It’s easier to read using async / await (but be aware that async / await is not runloop aware, so don’t use that in default project setup).

Your code is the same as:

model (params) {
  let personalPromise = this.get ('ajax')
    .post ('/personalProfile', {
      data: {contactId: params.contact_id}
    });

  personalPromise.then(
    profileResponse => rsvp.hash({
      // Gets the CPA's Name
      CPA: this.get ('ajax').post ('/CPA', {
        data: { CPAId: profileResponse.ProfessionalContact_CPA }
      }),
      //Gets the Attorney's name
      attorney: this.get ('ajax').post ('/attorney', {
        data: { attorneyId: profileResponse.ProfessionalContact_Attorney }
      })
    });
  );

  return personalPromise;
}

#5

@jelhan Thanks for your reply, the way you write wrote it has clicked much better. Thanks for the help!


#6

I’ve seen this style before and never fully understood it. Can you explain to me why this style:

model() {
  return new Promise((resolve, reject) => {
    outerPromise.then((outerPromiseResolved) => {
      innerPromise.then((innerPromiseResolved) => {
         resolve(innerPromiseResolved);
       });
    });
  });
}

Is more preferred then this style:

model() {
  return outerPromise()
    .then(outerPromiseValue => innerPromise(outerPromiseValue))
    .then(innerPromiseValue => finalPromise(innerPromiseValue));
}

#7

Maybe I’m just wrong here? I’ve tried to demonstrate the issue and it’s not behaving like I stated: https://ember-twiddle.com/6acdc79244d53074e24f2908de3a9c6f?openFiles=templates.application.hbs%2C

I expected this code to resolve with ‘a’ but it’s resolving with ‘b’:

import { resolve } from 'rsvp';

model () {
  return resolve('a')
    .then(() => resolve('b'));
}

I’m totally confused right now to be honest cause I remember quite well that I had issues with Promise chains like that…


#8

Promises will resolve to the last return value in the chain. This by design.

If we remove the syntax sugar from your example:

model() {
  let firstPromise = resolve('a');  // => Resolves to "a"
  let secondPromise = resolve('b'); // => Resolves to "b"
  let thirdPromise = firstPromise.then(function(firstPromiseValue) {
    return secondPromise;
  }); // => Resolves to secondPromise which resolves to "b"
  return thirdPromise;
}

This is contrived but it shows how promises chain and how the resolution changes based on the return values. If a promise (or then function returns a value the whole chain will resolve to that value (up to that point in the chain) if it returns a promise then the chain will wait for that returned promise to resolve and use that value to continue the chain.

Here is a good break down: https://javascript.info/promise-chaining

To change your example to return a instead try this contrived example:

import { resolve, all } from 'rsvp';

model() {
  return resolve('a')
    .then((value) => all([value, resolve('b')]))
    .then(([value]) => value);
}

#9

To accomplish what you wish try this code:

import Route from '@ember/routing/route';
import { hash } from 'rsvp';

export default Route.extend({
  model(params) {
    return this.fetchProfile(params.contact_id).then(profile => {
      return hash({
        profile,
        CPA: this.fetchCPA(profile.ProfessionalContact_CPA),
        attorney: this.fetchAttorney(profile.ProfessionalContact_Attorney)
      });
    });
  },

  fetchProfile(contactId) {
    return this.get('ajax').post('/personalProfile', { data: { contactId } });
  },

  fetchCPA(CPAId) {
    return this.get('ajax').post('/CPA', { data: { CPAId } });
  },

  fetchAttorney(attorneyId) {
    return this.get('ajax').post('/attorney', { data: { attorneyId } });
  }
});

#10

I think this example is causing confusion, probably because it’s representing something abstract without a real example.

If we assume we already have outerPromise and innerPromise, then there’s no reason to say new Promise. It would indeed just be the simpler pattern like

You only need to say new Promise when you are dealing with an API that doesn’t already provide promises. That almost always means converting a callback to a promise:

return new Promise((resolve, reject) => {
  doSomethingAsync((error, result) => {
    if (error) {
       reject(error);
    } else {
       resolve(result);
    }
  });
});

Whenever you do need this pattern, it’s good to package it up into its own function, so that the rest of your code can be 100% promise-driven and not a mix between callback-driven and promise-driven.

As for the difference between nesting and chaining:

// nesting
firstStep().then(firstResult => {
  return secondStep().then(secondResult => {
    return thirdStep();
  });
});

// chaining
firstStep.then(secondStep).then(thirdStep);

It mostly comes down to the data flow data flow and error handling.

By data flow, I mean which steps needs the results of which previous steps. If each step only needs the value from the preceding step, you can chain, and that is probably the clearest thing to do. But if any step needs the values from several earlier steps, it will be easiest to nest them.

By error handling, I mean that if you’re adding .catch to deal with errors, nesting and chaining can result in different things being covered by the catch. If you only want the catch around _some_of the steps, you will need nesting. If you want the catch around all of the steps, you can use chaining and put the catch at the very end.


#11

All true; however, you can also use Promise.all to package up previous values without loosing them down the chain. Both patterns work and it really depends on personal aesthetics at that point.


#12

One challenge with Promise.all can be that any one of the endpoints having an error will cause the entire Promise.all to fail (unless you wrap the individual promises each in a new promise that always succeeds). In the context of Ember and Routing, this can mean your model errors and shows the error state instead of the expect route, which may or may not be intended.


#13

Please don’t do this. Use RSVP.allSettled if you want them all to run to completion despite errors.


#14

I use RSVP because of Ember, but I still prefer to stay with as close to basic JS as I can. And I’d rather have each item in the resulting array be a promise, not an object with the results. I’ll probably just stick to wrapping promises when needed. But thanks for the heads-up about allSettled - if it looks like it’ll come in handy, now I know.