Асинхронный джиттер
Мы можем думать о каждой асинхронной операции (Promise или setTimeout) как об отдельном асинхронном потоке. В реальном процессе программирования в большинстве случаев мы не контролируем асинхронные потоки и позволяем им постоять за себя, но это легко может вызвать некоторые проблемы.
Асинхронный стабилизатор
Следующий псевдокод описывает основные моменты нашей ежедневной обработки антишейков:
const debounce = (func, delay = 500) => {
let timeout = 0;
return (...args) => {
// 如果没有阻断
if(!timeout){
// 那么开始阻断
timeout = setTimeout(() => {
// delay之后解除阻断
timeout = 0;
}, delay)
// 立即执行
func(...args);
} else {
// 什么也不做
}
}
}
Видно, что основная идея этого антишейка заключается в том, чтобыdelay
Поведение во время периода задержки блокируется. Мы можем думать об этом как оповеденческая стабилизация, который часто используется в сценариях привязки событий, таких как щелчки и движения мыши.
Однако в асинхронных операциях, таких как описанный выше случай 1, если поведение асинхронного запроса компонентов в цикле for заблокировано, данные, требуемые каждым компонентом, будут недоступны, и их нельзя будет инициировать с помощью разрешения или отклонения. логика. В этом случае нам нужно сделатьСтабилизация данных.
Процесс защиты от сотрясения данных можно условно разделить на следующие этапы:
- положить начало
- вернутьПосле этого ко всем ожидающим запросамобщие данные;
- Сбросить состояние и данные, когда все ожидающие очереди опустеют.
Проще говоря, это передать набор асинхронных запросов первому запросу, а остальные просто ждать результата запроса. Это чем-то похоже на отношения между процессами и потоками. В то же время, поскольку URL-адреса разных http-запросов различаются, возвращаемые данные также различаются, поэтому каждый асинхронный запрос необходимо группировать по URL-адресу, а общие данные также необходимо изолировать по URL-адресу.
простая реализация
Сначала смоделируйте асинхронный запрос:
let somePromise = (key) => new Promise((resolve, reject) => {
setTimeout(() => {
resolve([key, Math.random()]);
}, 500 + 500 * Math.random())
});
Мы можем использовать параметрыurl
Групповые запросы httpGet.
Ниже представлен анти-шейк на основе Promise
const debouncePromise = (factory, keyIndex = 0, delay = 50) => {
// 共享数据空间
const cache = {};
return (...args) => new Promise((resolve, reject) => {
// 获取缓存分组
let key = args[keyIndex];
let state = cache[key];
if(!state){
state = { status: 0, taskCount: 0 };
cache[key] = state;
}
// 首发请求,可看为主线程
if(state.status === 0){
// 锁定状态,挂起其他请求。
state.status = 1;
factory(...args).then(result => {
// 结束自身异步行为
resolve(result);
// 共享数据
state.result = result;
// 解锁状态,通知其他请求。
state.status = 2;
}, err => {
reject(err);
state.result = err;
// 解锁状态,通知其他请求。
state.status = 3;
})
}
// 其他请求,可看为辅线程,仅等待主线程结果。
else if(state.status === 1){
// 任务数+1
state.taskCount += 1;
const waitingHandle = setInterval(() => {
// 已解锁状态,2或3
if(state.status > 1){
// 清理等待循环
clearInterval(waitingHandle);
// 处理结果
(state.status === 2 ? resolve : reject)(state.result);
// 任务数-1
state.taskCount -= 1;
// 如果任务数归零,说明自身是最后一个线程
// reset状态和数据
if(state.taskCount <= 0){
delete cache[key];
}
}
}, delay)
}
})
}
Тестовый код:
somePromise('aaaaa').then(res => console.log(res))
somePromise('aaaaa').then(res => console.log(res))
somePromise('aaaaa').then(res => console.log(res))
// debounce it
somePromise = debouncePromise(somePromise)
somePromise('bbb').then(res => console.log(res))
somePromise('bbb').then(res => console.log(res))
somePromise('bbb').then(res => console.log(res))
somePromise('cc').then(res => console.log(res))
somePromise('cc').then(res => console.log(res))
somePromise('cc').then(res => console.log(res))
// 低于delay阈值再次推入队列
// 期待结果应与上面的bbb分组一致。
setTimeout(() => {
somePromise('bbb').then(res => console.log(res))
somePromise('bbb').then(res => console.log(res))
somePromise('bbb').then(res => console.log(res))
}, 10)
// 高于delay阈值重新发起请求
// 期待结果应与上面的bbb分组不一致。
setTimeout(() => {
somePromise('bbb').then(res => console.log(res))
somePromise('bbb').then(res => console.log(res))
somePromise('bbb').then(res => console.log(res))
}, 1000)
Результаты:
// 防抖前,每次请求结果抖动。
["aaaaa", 0.6301757853487]
["aaaaa", 0.2816070377500479]
["aaaaa", 0.009064307010989259]
//防抖后,delay阈值内结果不抖动
["bbb", 0.43005402041935437]
["bbb", 0.43005402041935437]
["bbb", 0.43005402041935437]
["cc", 0.956314414078062]
["cc", 0.956314414078062]
["cc", 0.956314414078062]
// delay阈值内认为是抖动,保持数据共享
["bbb", 0.43005402041935437]
["bbb", 0.43005402041935437]
["bbb", 0.43005402041935437]
// delay阈值外认为是新的请求
["bbb", 0.7923457809536392]
["bbb", 0.7923457809536392]
["bbb", 0.7923457809536392]
Результаты выполнения соответствуют ожиданиям.
Образец кода:codespray.IO/Marvin_2019…