Моя реализация запроса debounce axios навсегда оставила обещание в состоянии ожидания, есть ли лучший способ? - PullRequest
3 голосов
/ 30 апреля 2019

Мне нужна простая функция debounce с немедленным всегда true.
Не прибегая к lodash и с помощью Может кто-нибудь объяснить функцию «debounce» в Javascript , я реализовал ее следующим образом,

function debounce(func, wait) {
    var timeout;
    return function() {
        if (!timeout) func.apply(this, arguments);
        clearTimeout(timeout);
        timeout = setTimeout(()=>{timeout = null}, wait);
    };
};

Работает как положено, пока мне не нужно отменить запрос axios. Предполагая, что у меня есть отклоненный метод axios, я хотел бы, чтобы вызывающий метод был как обычно, а это значит, что мой отклоненный метод axios должен вернуть обещание, которому я верю.

   //the calling method should not change   
   debounced_axios().then(res => {...}).catch(err => {...}) 

Суть оригинальной реализации debounce заключается в том, чтобы просто запустить func один раз в период ожидания, но как мне просто вернуть одно обещание в период ожидания?

Тогда я придумал следующее решение

all_timers = {}
function debounce_axios(input, wait) {
    return new Promise((resolve, reject) => {
        let timer = all_timers.[input] //check if it is a repeated request, pseudo code
        if (!timer) {
            axios(input).then(res=>{
                resolve(res)
            }).catch(err => {
                reject(err)
            })
        }
        clearTimeout(timer);
        timer = setTimeout(()=>{timer = null}, wait);
        all_timers[input] = timer
    };
};

Итак, суть моего debounce_axios заключается в том, чтобы обещание оставалось в состоянии ожидания для повторного запроса. Затем вызывающий метод debounced_axios().then(res => {...}).catch(err => {...}) менять не нужно.

Ответ здесь Являются ли JavaScript вечно ожидающие обещания плохими? сказал: «Не должно быть побочных эффектов».

Но я все еще не уверен на 100%, позволять ли обещанию оставаться в ожидании вечно.

Другой вопрос заключается в том, что Шаблоны Promise Anti предлагают не создавать ненужных обещаний. Но в моем случае создание нового обещания кажется необходимым.

В двух словах, есть ли простой способ отменить запрос axios (или любой запрос возвращает обещание)?

Ответы [ 2 ]

3 голосов
/ 30 апреля 2019

Но я все еще не на 100% уверен, что обещание останется в ожидании навсегда.

Я согласен, что это не очень хорошая идея. Лучшим подходом было бы поместить всю цепочку обещаний в обсуждаемую функцию.

Другим вариантом будет возвращение кэшированного значения, когда отклоненный вызов не инициирует новый запрос. Это решит вашу проблему: вам всегда нужно возвращать обещание:

function debounce(func, wait) {
    var timeout, value;
    return function() {
        if (!timeout) value = func.apply(this, arguments);
        clearTimeout(timeout);
        timeout = setTimeout(() => {
            timeout = value = null;
        }, wait);
        return value;
    };
}

Конечно, это будет означать, что в некоторых случаях будет вызываться несколько обработчиков then, когда ваш запрос завершится. Это зависит от вашего приложения, является ли это проблемой или просто лишней работой.

Другой вопрос заключается в том, что шаблоны Promise Anti предлагали не создавать ненужных обещаний. Но в моем случае создание нового обещания кажется необходимым.

Необходимо только одно обещание: когда вы создаете никогда не выполненное. Вы можете написать это как

function debounce(func, wait) {
    var timeout;
    const never = new Promise(resolve => {/* do nothing*/});
    return function() {
        const result = timeout ? never : func.apply(this, arguments);
        clearTimeout(timeout);
        timeout = setTimeout(() => {
            timeout = null;
        }, wait);
        return result;
    };
}

Или, по крайней мере, избегать части .then(resolve).catch(reject). Лучше пиши

function debounce(func, wait) {
    var timeout;
    return function() {
        return new Promise(resolve => {
            if (!timeout) resolve(func.apply(this, arguments));
//                        ^^^^^^^
            clearTimeout(timeout);
            timeout = setTimeout(() => {
                timeout = null;
            }, wait);
        });
    };
}

И если вы решите отклонить обещание в случае, если тайм-аут еще не наступил (чтобы вызывающий код мог обработать отклонение), вам также не нужно new Promise:

function debounce(func, wait) {
    var timeout;
    return function() {
        const result = timeout
          ? Promise.reject(new Error("called during debounce period"))
          : Promise.resolve(func.apply(this, arguments));
        clearTimeout(timeout);
        timeout = setTimeout(() => {
            timeout = null;
        }, wait);
        return result;
    };
}
1 голос
/ 30 апреля 2019

По сути, вам нужно поделиться результатом вашей функции debounce.В вашем случае это обещание:

const debouncedGetData = debounce(getData, 500)
let promiseCount = 0
let resultCount = 0
test()

function test() {
  console.log('start')
  callDebouncedThreeTimes()
  setTimeout(callDebouncedThreeTimes, 200)
  setTimeout(callDebouncedThreeTimes, 900)
}

function callDebouncedThreeTimes () {
   for (let i=0; i<3; i++) {
      debouncedGetData().then(r => {
        console.log('Result count:', ++resultCount)
        console.log('r', r)
      })
   }
}

function debounce(func, wait) {
    let waiting;
    let sharedResult;
    return function() {
        // first call will create the promise|value here
        if (!waiting) {
          setTimeout(clearWait, wait)
          waiting = true
          sharedResult = func.apply(this, arguments);
        }
        // else new calls within waitTime will be discarded but shared the result from first call

        function clearWait() {
          waiting = null
          sharedResult = null
        }

        return sharedResult
    };
}

function getData () {
  console.log('Promise count:', ++promiseCount)
  return new Promise((resolve, reject) => {
    setTimeout(() => {
       resolve(666)
    }, 1000)
  })
}
...