1

I'm using Ajax with JQuery to fetch data from an API that only returns 100 records at a time. If my query gives a result with more than 100 records, the API will include an "offset" parameter in the response. I have to use this offset parameter in a new API call to get the next 100 records. The API will include a new offset parameter if there's even more records to fetch. And so on until all records are fetched.

As you can see I've solved this by having the function call itself until the "offset" parameter is no longer included. I.e. until there are no more records to fetch.

Because of this behavior of the API, I cannot use the Ajax method's own .done-function, since it would be executed multiple times (for each iteration of the Ajax method).

How can adjust the function below to return a promise when all Ajax calls have been done?

function getContracts(offset) {

    var data = {};

    if (offset !== undefined) {
      data["offset"] = offset;
    }

    $.ajax({
      url: url,
      headers: {
        Authorization: apiKey
      },
      data: data,
      success: function(result){

        $.each(result.records, function() {        
            contracts.push(this);
        });

        if (result.hasOwnProperty("offset")) {
          getContracts(result.offset);
        }

      }

    });

  }

Real and full code as requested:

  var objectContracts = [];
  var landContracts = [];
  var locations = [];
  var customers = [];
  var landOwners = [];
  var frameworkAgreements = [];

  function getObjectContracts(offset) {

    return new Promise((resolve, reject) => {

      var data = {};

      data["view"] = 'Alla Objektsavtal';

      if (offset !== undefined) {
        data["offset"] = offset;
      }

      $.ajax({
        url: url + "Objektsavtal",
        headers: {
          Authorization: apiKey
        },
        data: data,
        success: function(result){

          $.each(result.records, function() {        
              objectContracts.push(this);
          });

          if (result.hasOwnProperty("offset")) {
            getObjectContracts(result.offset);
          } else {
            resolve();
          }

        }
      });

    });

  }

  function getLandContracts(offset) {

    return new Promise((resolve, reject) => {

      var data = {};

      data["view"] = 'Alla Markavtal';

      if (offset !== undefined) {
        data["offset"] = offset;
      }

      $.ajax({
        url: url + "Markavtal",
        headers: {
          Authorization: apiKey
        },
        data: data,
        success: function(result){

          $.each(result.records, function() {        
              landContracts.push(this);
          });

          if (result.hasOwnProperty("offset")) {
            getLandContracts(result.offset);
          } else {
            resolve();
          }

        }

      });

    });

  }

  function getLocations(offset) {

    return new Promise((resolve, reject) => {

      var data = {};

      data["view"] = 'Alla Uppställningsplatser';

      if (offset !== undefined) {
        data["offset"] = offset;
      }

      $.ajax({
        url: url + "Uppställningsplatser",
        headers: {
          Authorization: apiKey
        },
        data: data,
        success: function(result){

          $.each(result.records, function() {        
              locations.push(this);
          });

          if (result.hasOwnProperty("offset")) {
            getLocations(result.offset);
          } else {
            resolve();
          }

        }

      });

    });

  }

  function getCustomers(offset) {

    return new Promise((resolve, reject) => {

      var data = {};

      data["view"] = 'Alla Kunder';

      if (offset !== undefined) {
        data["offset"] = offset;
      }

      $.ajax({
        url: url + "Kunder",
        headers: {
          Authorization: apiKey
        },
        data: data,
        success: function(result){

          $.each(result.records, function() {        
              customers.push(this);
          });

          if (result.hasOwnProperty("offset")) {
            getCustomers(result.offset);
          } else {
            resolve();
          }

        }

      });

    });

  }

  function getLandOwners(offset) {

    return new Promise((resolve, reject) => {

      var data = {};

      data["view"] = 'Alla Markägare';

      if (offset !== undefined) {
        data["offset"] = offset;
      }

      $.ajax({
        url: url + "Markägare",
        headers: {
          Authorization: apiKey
        },
        data: data,
        success: function(result){

          $.each(result.records, function() {        
              landOwners.push(this);
          });

          if (result.hasOwnProperty("offset")) {
            getLandOwners(result.offset);
          } else {
            resolve();
          }

        }

      });

    });

  }

  function getFrameworkAgreements(offset) {

    return new Promise((resolve, reject) => {

      var data = {};

      data["view"] = 'Alla Ramavtal';

      if (offset !== undefined) {
        data["offset"] = offset;
      }

      $.ajax({
        url: url + "Ramavtal",
        headers: {
          Authorization: apiKey
        },
        data: data,
        success: function(result){

          $.each(result.records, function() {        
              frameworkAgreements.push(this);
          });

          if (result.hasOwnProperty("offset")) {
            getFrameworkAgreements(result.offset);
          } else {
            resolve();
          }

        }

      });

    });

  }
Rawland Hustle
  • 781
  • 1
  • 10
  • 16
  • Avoid the [`Promise` constructor antipattern](https://stackoverflow.com/q/23803743/1048572?What-is-the-promise-construction-antipattern-and-how-to-avoid-it)! The `$.ajax` function already returns a promise, you can [just use that](https://stackoverflow.com/a/31327725/1048572). – Bergi Jul 17 '19 at 19:12

1 Answers1

2

If I've understood your question perfectly, you want to resolve a Promise if there is no offset in the response from your Ajax request.

I haven't tested this code but you can do something like this:

function getContracts(offset) {
  return new Promise((resolve, reject) => {
    var data = {};

    if (offset !== undefined) {
      data['offset'] = offset;
    }

    $.ajax({
      url: url,
      headers: {
        Authorization: apiKey,
      },
      data: data,
      success: function(result) {
        $.each(result.records, function() {
          contracts.push(this);
        });

        if (result.hasOwnProperty('offset')) {
          getContracts(result.offset);
        } else {
          // I guess this is what you want
          // If there is no offset property => resolve the promise
          resolve('Your result goes here');
        }
      },
    });
  });
}

See the else block.
You can pass your final result (whatever you want to achieve after the completion of your task) inside the resolve. For example, you can create an array and append your result to that and at the end, you can pass that array inside resolve.

You can resolve this using .then() or async/await

async () => {
  const result = await getContracts(offset);
};

or

getContracts(offset).then(result => { console.log(result) });

If you see some Unhandled Promise Rejection warning/error, you can always use try/catch block with async/await and .catch after .then.

EDIT:

  • First, you're not passing anything inside resolve. Whatever you pass inside the resolve will be reflected in .then(result).
  • Second, you have global variables and storing all your data inside them. So now you don't need to pass them inside the resolve but this is not a good approach because any function or the code outside can modify it. So I'll give you one example.
function getObjectContracts(offset) {
  return new Promise((resolve, reject) => {
    var data = {};

    const objectContracts = [];

    data['view'] = 'Alla Objektsavtal';

    if (offset !== undefined) {
      data['offset'] = offset;
    }

    $.ajax({
      url: url + 'Objektsavtal',
      headers: {
        Authorization: apiKey,
      },
      data: data,
      success: function(result) {
        $.each(result.records, function() {
          objectContracts.push(this);
        });

        if (result.hasOwnProperty('offset')) {
          getObjectContracts(result.offset);
        } else {
          resolve(objectContracts);
        }
      },
    });
  });
}

Now, the other question is, how to resolve all these promises at once.

const finalFunction = async () => {
  const [result1, result2, result3] = await Promise.all([
    getObjectContracts(offset1),
    getLandContracts(offset2),
    getLocations(offset3),
  ]);

  console.log(result1, result2, result3);
};

finalFunction();
Apal Shah
  • 680
  • 8
  • 17
  • Thanks! What should I replace the string 'Your result goes here' with? And when you say "You can resolve this using .then()", does that mean that I can do getContracts().then()? – Rawland Hustle Jul 17 '19 at 15:24
  • I found the answer to the string inside the resolve method. "Optional arguments that are passed to the doneCallbacks". I also noticed that I can use getContracts().then(). I am going to apply your solution to several functions. How can I know when all the functions' promises are resolved? Is it with promiseAll()? In that case, should turn every function in to a variable? Like this: var getContracts = function () {...} – Rawland Hustle Jul 17 '19 at 15:41
  • I've updated my answer. Comment here if you still don't understand this. – Apal Shah Jul 17 '19 at 15:55
  • Thank you! Please see my question in the comment above, about wating for multiple functions with promises. How do I know when they are ALL done? – Rawland Hustle Jul 17 '19 at 15:58
  • @RawlandHustle [Promise.all](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all) – Charlie Schliesser Jul 17 '19 at 16:04
  • 1
    If you're using `.then()`, you don't have to worry about anything but after sometime, you'll be stuck in the callback hell. If you use async/await and the inputs/outputs of your functions are not dependant on each other, it'll downgrade the performance. And you're correct, you have to use Promise.all() for this. See the link below it has the answer for your question. https://alligator.io/js/async-functions/ – Apal Shah Jul 17 '19 at 16:05
  • Also, your way of resolving with ".then" does not work for some reason. I'm passing the result as an argument like this: .resolve(result) but nothing gets logged in the concole. – Rawland Hustle Jul 17 '19 at 16:06
  • Also, it would be great if you can accept the answer if it has solved your problem. – Apal Shah Jul 17 '19 at 16:14
  • I have added my actual code to my initial question. It has 6 functions in it. How do I do something when all functions have returned their promises. Promise.all does not seem to work. I have accepted your answer now. Thanks! – Rawland Hustle Jul 17 '19 at 16:16
  • I'm sorry, but that does not work. I have adjusted all my functions like the one in your example. The code below logs nothing to the console. const finalFunction = async () => { const [result1, result2, result3, result4, result5, result6] = await Promise.all([ getObjectContracts(), getLandContracts(), getLocations(), getCustomers(), getLandOwners(), getFrameworkAgreements() ]); console.log(result1, result2, result3, result4, result5, result6); }; – Rawland Hustle Jul 17 '19 at 16:48
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/196586/discussion-between-apal-shah-and-rawland-hustle). – Apal Shah Jul 17 '19 at 16:49