Накапливать идентификаторы, чтобы сделать один запрос AJAX - PullRequest
0 голосов
/ 07 мая 2018

У меня есть несколько мест, где мне нужно сделать ajax-запросы для получения элементов, соответствующих некоторым идентификаторам. Тем не менее, я хочу сделать только один запрос, накапливая эти идентификаторы и опровергая фактический метод, который делает запрос ajax ... До сих пор я придумал этот код, но он кажется уродливым / не пригодным для повторного использования.

Существует ли какой-либо более простой / рекомендуемый метод для достижения аналогичных результатов без совместного использования resolve / promise переменных, как я сделал здесь?

Вот скрипка

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>

Ответы [ 2 ]

0 голосов
/ 10 мая 2018

Мне было проще написать debounce() функцию с поведением полностью под моим контролем, чем полагаться на метод библиотеки.

В частности, я изо всех сил старался изобрести поведение, отличное от поведения в вопросе, с помощью которого (если я правильно понимаю) первый и, возможно, только , запрос быстрой последовательности должен дождаться истечения задержки отмены.

В приведенном ниже коде вместо оригинального getItemsPromise debouncePromise используется для обозначения периода отладки, в течение которого выборка подавляется и разрешается накапливать данные запроса. Из состояния покоя (всякий раз, когда debouncePromise === null), следующий вызов fetch() извлечет данные как можно скорее (следующий тик). Отменяются только второй и последующие вызовы до тех пор, пока не истечет период отмены и экземпляр debounce() не вернется в состояние покоя. Я думаю, что такая же действительная парадигма «отказов», как и оригинал, и, возможно, лучше. (Если нет, то fetch() можно слегка изменить, чтобы придать исходное поведение).

Кроме того, различия незначительны:

  • беспорядочные экстерналии resolve и reject избегаются
  • в попытке сохранить универсальный debounce() передается функция resultsFilter в дополнение к fetcher и delay.

Дополнительные комментарии в коде.

function debounce(fetcher, resultsFilter, delay) {
    let idsToFetch = [],
        debouncePromise = null;
    function reset() { // utility funtion - keeps code below clean and DRY
        let idsToFetch_ = idsToFetch;
        idsToFetch = [];
        return idsToFetch_;
    }
    function fetch(ids) {
        idsToFetch = idsToFetch.concat(ids.filter(id => !idsToFetch.includes(id))); // swapped around so as not to reverse the order.
        if (!debouncePromise) {
            // set up the debounce period, and what is to happen when it expires.
            debouncePromise = new Promise(resolve => {
                setTimeout(resolve, delay);
            }).then(() => {
                // on expiry of the debounce period ...
                debouncePromise = null; // ... return to quiescent state.
                return fetcher(reset()); // ... fetch (and deliver) data for all request data accumulated in the debounce period.
            });
            // *** First call of this debounce period - FETCH IMMEDIATELY ***
            return Promise.resolve(reset()).then(fetcher); // (1) ensure fetcher is called asynchronously (as above). (2) resultsFilter is not necessary here.
        } else {
            return debouncePromise.then(res => resultsFilter(ids, res)); // when debouncePromise exists, return it with chained filter to give only the results for these ids.
        }
    }
    return fetch;
}

Пример использования:

function fetchItems(ids) {
    const fakeData = [
        { 'id': 1, 'name': 'foo' },
        { 'id': 2, 'name': 'bar' },
        { 'id': 3, 'name': 'baz' },
        { 'id': 4, 'name': 'zaz' }
    ];
    if (ids.length > 0) {
        return new Promise(resolve => { // simulate ajax request
            setTimeout(resolve, 400);
        }).then(() => {
            return ids.map(id => fakeData.find(item => item.id == id));
        });
    } else {
        return Promise.resolve([]);
    }
}
function filterResults(ids, results) {
    return results.filter(item => ids.includes(item.id));
}

// ******************************************************
let getItems = debounce(fetchItems, filterResults, 500);
// ******************************************************

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);
setTimeout(() => {
    console.log('fourth request start');
    getItems([1, 4]).then(res => console.log('fourth result:', res));
}, 2000);

Протестировано в меру этой скрипки

0 голосов
/ 07 мая 2018

Мне удалось каким-то образом инкапсулировать логику, создав генератор функций, который содержит две предыдущие функции, например:

   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;
        });
      }
      const currentPromise = getItemsPromise;
      fetchItems();

      return currentPromise
        .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>
...