我对debounce axios请求的实现将promise永远保留在待处理状态,有没有更好的方法?

时间:2019-04-30 11:14:54

标签: javascript promise axios es6-promise debouncing

我需要一个简单的反跳功能,且即时始终为真。
在不求助于lodash的情况下,并借助Can someone explain the "debounce" function in 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方法应该返回我相信的promise。

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

原始去抖动实现的本质是仅在等待时间范围内运行一次功能一次,但是如何在等待时间范围内返回一个承诺?

然后我想出了以下解决方案

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的本质是让promise处于重复请求的待处理状态。然后,调用方法debounced_axios().then(res => {...}).catch(err => {...})不需要更改。

这里的答案Are JavaScript forever-pending promises bad?说:“应该没有副作用。”

但是我仍然不能百分百地保证诺言永远待定。

另一个问题是Promise Anti patterns建议不要创建不必要的承诺。但就我而言,创建新的承诺似乎是必要的。

简而言之,有没有一种简单的方法可以对axios请求(或任何请求返回promise)进行去抖动?

2 个答案:

答案 0 :(得分:3)

  

但是我仍然不能百分百地保证诺言永远待定。

我同意这不是一个好主意。更好的方法是将整个promise链移到去抖动功能中。

另一种选择是当去抖动的呼叫未触发新请求时,返回一个缓存的值。这样可以解决您始终需要兑现承诺的问题:

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 :(得分:1)

本质上,您需要共享去抖动功能的结果。对于您而言,这就是一个承诺:

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)
  })
}