我对 debounce axios 请求的实现使承诺永远处于挂起状态,有没有更好的方法?

Posted

技术标签:

【中文标题】我对 debounce axios 请求的实现使承诺永远处于挂起状态,有没有更好的方法?【英文标题】:My implementation of debounce axios request left the promise in pending state forever, is there a better way? 【发布时间】:2019-09-19 01:02:29 【问题描述】:

我需要一个简单的 debounce 函数,它总是立即为真。 在不求助于 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 方法应该返回我相信的承诺。

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

原始去抖动实现的本质是在等待时间范围内只运行 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的本质是让promise对于重复请求保持在pending状态。那么调用方法debounced_axios().then(res => ...).catch(err => ...)就不需要改变了。

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

但我仍然不能 100% 确定让承诺永远处于未决状态。

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

简而言之,有没有一种简单的方法来消除 axios 请求(或任何请求返回承诺)?

【问题讨论】:

.then(res => ...).catch(err => ...)”到底是做什么的?为什么不简单地把它放在你用来创建debounced_axios 的函数中? 各种偶数处理程序来操作 DOM,因此我无法在 debounced_axios 中输入它们 好吧,也许你不应该再叫它debounced_axious,但是为什么不完全消除整个事情呢? 是的,我同意去抖动 ui 可能是一个更好的主意。但事实上它是一个 SPA 应用程序,代码库很大,需要修改的地方太多,而我们确实将 axios 方法包装在一个 util 类中,因此很容易修改它。顺便说一句,我从你那里得到了这个想法。谢谢 如果您不喜欢永远未决的承诺,是否可以立即拒绝过早提出的请求? 【参考方案1】:

但我仍然不能 100% 确定让承诺永远处于未决状态。

我同意这不是一个好主意。更好的方法是将整个 Promise 链移动到 debounced 函数中。

另一种选择是在去抖动调用未触发新请求时返回缓存值。这将解决您始终需要返回承诺的问题:

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;
    ;

【讨论】:

您好,我正在验证您的每一个建议。对于返回缓存值,我不确定它是否会起作用。因为我假设缓存的承诺将在调用方法的承诺链中被多次调用,对吧? 我以前从未缓存过承诺,所以我想我是在问一个新手问题。那么缓存 Promise 并重用它是安全的吗?使用缓存的 Promise 运行 Promise 链是否安全? 是的,caching promises 完全正常并且运行良好。 Promise 不是“调用”的,它是一个结果值 - 请求只会发出一次,但响应将传递给在 .then() 注册的尽可能多的处理程序。 您好,我仍在验证您的答案:$。对于“或者至少避免使用 .then(resolve).catch(reject) 部分。”我认为您为去抖动调用返回了一个未决的永远承诺,就像创建从未解决的承诺一样。我们回到第一方,即我最初的问题是这样做好吗?不是这样吗? @Qiulang 是的,我认为至少有 3 种更好的方法,但是如果您仍然想做,那么至少要正确地做(如第二个或第三个 sn-p 所示)。 【参考方案2】:

本质上,您需要分享去抖动功能的结果。在你的情况下,这是一个承诺:

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

【讨论】:

我认为是不正确的 b/c (a) 正常的去抖动方法应该返回去抖动的方法 (b) 在 axios 情况下,当去抖动的情况没有返回任何返回时,调用方法会得到 UnhandledPromiseRejectionWarning: TypeError: (...).then 不是函数。 我相信这里的 debounce 函数可以用于任何类型的函数,因为它不依赖于 promises 谢谢,我会核实并回复您。

以上是关于我对 debounce axios 请求的实现使承诺永远处于挂起状态,有没有更好的方法?的主要内容,如果未能解决你的问题,请参考以下文章

axios取消功能的设计与实现

js 实现一个debounce防抖函数

axios get请求后访问json数据

debounce(防抖)和throttle(节流)

axios请求封装,请求异常统一处理

Typescript:如何正确处理 axios 请求?