Накапливать идентификаторы, чтобы сделать один запрос AJAX
У меня есть несколько мест, где мне нужно сделать 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 ответа
Мне было проще написать 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);
Мне удалось каким-то образом инкапсулировать логику, создав генератор функций, который содержит две предыдущие функции, например:
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>