Accumulate ids to make a single ajax request

Posted on

Problem

I have multiple places where I need to make ajax requests to fetch the items corresponding to some ids. However, I only want to make a single request by accumulating these ids and debouncing the actual method that makes the ajax request…So far I’ve come up with this code, but it just feels ugly/non-reusable.

Is there any simpler/recommended method to achieve similar results without sharing resolve/promise variables like I did here?

Here’s a fiddle

const fakeData = [{
    id: 1,
    name: 'foo'
  },
  {
    id: 2,
    name: 'bar'
  },
  {
    id: 3,
    name: 'baz'
  }
];

let idsToFetch = [];

let getItemsPromise, resolve, reject;

const fetchItems = _.debounce(() => {
  console.log('fetching items...');
  const currentResolve = resolve;
  const currentReject = reject;

  // simulating ajax request
  setTimeout(function() {
    const result = idsToFetch.map((id) => fakeData.find(item => item.id == id));
    currentResolve(result);
  }, 400);

  getItemsPromise = resolve = reject = null;
}, 500);

function getItems(ids) {
  idsToFetch = ids.filter((id) => !idsToFetch.includes(id)).concat(idsToFetch);
  if (!getItemsPromise) {
    getItemsPromise = new Promise((_resolve, _reject) => {
      resolve = _resolve;
      reject = _reject;
    });
  }

  fetchItems();

  return getItemsPromise
    .then((res) => {
      return res.filter((item) => ids.includes(item.id));
    })
}

setTimeout(() => {
  console.log('first request start');
  getItems([1]).then(res => console.log('first result:', res));
}, 100);
setTimeout(() => {
  console.log('second request start');
  getItems([1, 2]).then(res => console.log('second result:', res));
}, 200)
setTimeout(() => {
  console.log('third request start');
  getItems([1, 3]).then(res => console.log('third result:', res));
}, 300)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.min.js"></script>

Solution

I was able to somehow encapsulate the logic be creating a function generator that holds the previous two functions like this:

  const fakeData = [{
      id: 1,
      name: 'foo'
    },
    {
      id: 2,
      name: 'bar'
    },
    {
      id: 3,
      name: 'baz'
    }
  ];

  function makeGetter(fetchFunc, debounceTime = 400) {
    let idsToFetch = [];

    let getItemsPromise, resolve, reject;

    const fetchItems = _.debounce(() => {
      console.log('fetching items...');
      const currentResolve = resolve;
      const currentReject = reject;
      const currentIdsToFetch = idsToFetch;

      Promise.resolve(fetchFunc(currentIdsToFetch))
        .then(res => currentResolve(res))
        .catch(err => currentReject(err));

      getItemsPromise = resolve = reject = null;
      idsToFetch = [];
    }, debounceTime);

    const getItems = (ids) => {
      idsToFetch = ids.filter((id) => !idsToFetch.includes(id)).concat(idsToFetch);
      if (!getItemsPromise) {
        getItemsPromise = new Promise((_resolve, _reject) => {
          resolve = _resolve;
          reject = _reject;
        });
      }

      fetchItems();

      return getItemsPromise
        .then((res) => {
          return res.filter((item) => ids.includes(item.id));
        })
    }

    return getItems;
  }

  const getItems = makeGetter((ids) => {
    // simulating ajax request
    return new Promise((resolve, reject) => {
      setTimeout(function() {
        const result = ids.map((id) => fakeData.find(item => item.id == id));
        resolve(result);
      }, 400);
    })
  });


  setTimeout(() => {
    console.log('first request start');
    getItems([1]).then(res => console.log('first result:', res));
  }, 100);
  setTimeout(() => {
    console.log('second request start');
    getItems([1, 2]).then(res => console.log('second result:', res));
  }, 200)
  setTimeout(() => {
    console.log('third request start');
    getItems([1, 3]).then(res => console.log('third result:', res));
  }, 300)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.min.js"></script>

Leave a Reply

Your email address will not be published. Required fields are marked *