前端算法题: Promise如何清除的问题

一道题:实现一个promise防抖函数,如果多个fn同时调用,只希望执行最后一个,也就是只打印 3-->1

function takeLatest(fn) {
  // 实现promise防抖
}

const fn = takeLatest(
  (v) => new Promise(
    (resolve) => {
      setTimeout(() => { resolve(v); }, 1000); 
    }
  )
);

fn(1).then((v) => { console.log("1--->", v); });
fn(1).then((v) => { console.log("2--->", v); });
setTimeout(() => {
  fn(1)
  .then((v) => {
    console.log("3--->", v); 
  }); 
}, 200);

我的方法如下:

function takeLatest(fn) {
  let timer;
  return function () {
    if (timer) {
      clearInterval(timer)
    }
    const arg  = arguments;
    const context  = this;
    return new Promise((resolve) => {
      timer = setTimeout(async () => {
        resolve(await fn.call(context, ...arg))
      },0)
    })
  }
}

但是结果不太行,打印结果是

2---> 1
3---> 1

请求解答:

  1. 为什么会打印2-->1
  2. 该方法如何实现呢
阅读 3.4k
3 个回答

你的代码有几个问题

function takeLatest(fn) {
  let timer;
  return function () {
    if (timer) {
      // 应该用clearTimeout
      clearInterval(timer)
    }
    const arg  = arguments;
    const context  = this;
    return new Promise((resolve) => {
      timer = setTimeout(async () => {
        resolve(await fn.call(context, ...arg)) // 这里好像只是传参,没必要call
      },0) // 这里如果用0,那么就要比log 3先执行,所以有2个log
    })
  }
}
function takeLatest(fn) {
  // 实现promise防抖
    var time
    return function(v) {
        clearTimeout(time)
        return new Promise(resolve => {
            time = setTimeout(() => resolve(fn(v)), 300)
        })
    }
}

你有没有想过:
为什么1--->没有被打印,而2--->被打印了?
对比之下,答案很明显:因为 1---> 后面没有定时器,而 2---> 后面有定时器
那么,假如3---> 不是放在定时器里,而是像2--->一样直接紧跟着执行的呢?

当然,这里不用假如,改改代码就能够验证。

所以问题在于:你让一个延时 0 毫秒的延时器去等一个 200 毫秒的延时器,正常情况下 0毫秒的肯定会先执行回调,也就不会处理 200 毫秒的延时器的回调中需要防抖的操作了。

想亲自照料一只乌龟的一生,借此验证乌龟是否能活三百岁,首先你得比乌龟活得久吧。

所以你的防抖函数设计上应该是没问题的,延时的间隔不对,要覆盖这个用例,至少要大于 200 毫秒。

ts 版本

/**
 * 实现一个promise防抖函数,如果多个fn同时调用,只希望执行最后一个,也就是只打印 3-->1
 *
 * 1. 题目这种需求,应该指的是一个 Promise 尚未执行完成,新来一次调用,抛弃之前的结果, resolve 的是最后一次执行的结果
 *
 */

type AsyncFunc = (...items: any[]) => Promise<any>;

function getId() {
  return "" + new Date().getTime() + Math.random();
}

function takeLatest<T extends AsyncFunc>(fn: T) {
  type Value = Awaited<ReturnType<T>>;
  const item: {
    id?: string;
    resolve?: (value: Value) => void;
    reject?: (value: any) => void;
  } = {};
  return function (...items: Parameters<T>): Promise<Value> {
    return new Promise((resolve, reject) => {
      const id = getId();
      item.id = id;
      item.resolve = resolve;
      item.reject = reject;
      fn(...items)
        .then((data) => {
          if (id === item.id) {
            item.resolve?.(data);
          }
        })
        .catch((err) => {
          if (id === item.id) {
            item.reject?.(err);
          }
        })
        .finally(() => {
          if (id === item.id) {
            delete item.id;
            delete item.resolve;
            delete item.reject;
          }
        });
    });
  };
}

/** --------------------------------------------------------------------- */

/** 开始测试 */
const fn = takeLatest(
  (v) =>
    new Promise((resolve) => {
      setTimeout(() => {
        resolve(v);
      }, 1000);
    })
);

fn(1).then((v) => {
  console.log("1--->", v);
});
fn(1).then((v) => {
  console.log("2--->", v);
});
setTimeout(() => {
  fn(1).then((v) => {
    console.log("3--->", v);
  });
}, 200);
fn(1).then((v) => {
  console.log("1--->", v);
});
fn(1).then((v) => {
  console.log("2--->", v);
});
setTimeout(() => {
  fn(1).then((v) => {
    console.log("3--->", v);
  });
}, 200);
fn(1).then((v) => {
  console.log("1--->", v);
});
fn(1).then((v) => {
  console.log("2--->", v);
});
setTimeout(() => {
  fn(1).then((v) => {
    console.log("3--->", v);
  });
}, 200);

js 版本

"use strict";
/**
 * 实现一个promise防抖函数,如果多个fn同时调用,只希望执行最后一个,也就是只打印 3-->1
 *
 * 1. 题目这种需求,应该指的是一个 Promise 尚未执行完成,新来一次调用,抛弃之前的结果, resolve 的是最后一次执行的结果
 *
 */
function getId() {
  return "" + new Date().getTime() + Math.random();
}
function takeLatest(fn) {
  const item = {};
  return function (...items) {
    return new Promise((resolve, reject) => {
      const id = getId();
      item.id = id;
      item.resolve = resolve;
      item.reject = reject;
      fn(...items)
        .then((data) => {
          if (id === item.id) {
            item.resolve?.(data);
          }
        })
        .catch((err) => {
          if (id === item.id) {
            item.reject?.(err);
          }
        })
        .finally(() => {
          if (id === item.id) {
            delete item.id;
            delete item.resolve;
            delete item.reject;
          }
        });
    });
  };
}
/** --------------------------------------------------------------------- */
/** 开始测试 */
const fn = takeLatest(
  (v) =>
    new Promise((resolve) => {
      setTimeout(() => {
        resolve(v);
      }, 1000);
    })
);
fn(1).then((v) => {
  console.log("1--->", v);
});
fn(1).then((v) => {
  console.log("2--->", v);
});
setTimeout(() => {
  fn(1).then((v) => {
    console.log("3--->", v);
  });
}, 200);
fn(1).then((v) => {
  console.log("1--->", v);
});
fn(1).then((v) => {
  console.log("2--->", v);
});
setTimeout(() => {
  fn(1).then((v) => {
    console.log("3--->", v);
  });
}, 200);
fn(1).then((v) => {
  console.log("1--->", v);
});
fn(1).then((v) => {
  console.log("2--->", v);
});
setTimeout(() => {
  fn(1).then((v) => {
    console.log("3--->", v);
  });
}, 200);
撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题