Асинхронный стабилизатор

JavaScript

Асинхронный джиттер

Мы можем думать о каждой асинхронной операции (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 заблокировано, данные, требуемые каждым компонентом, будут недоступны, и их нельзя будет инициировать с помощью разрешения или отклонения. логика. В этом случае нам нужно сделатьСтабилизация данных.

Процесс защиты от сотрясения данных можно условно разделить на следующие этапы:

  1. положить начало
  2. вернутьПосле этого ко всем ожидающим запросамобщие данные;
  3. Сбросить состояние и данные, когда все ожидающие очереди опустеют.

Проще говоря, это передать набор асинхронных запросов первому запросу, а остальные просто ждать результата запроса. Это чем-то похоже на отношения между процессами и потоками. В то же время, поскольку 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…