js 如何利用promise实现一个函数多次调用时只执行最后一次

想要实现test函数在同步代码中多次调用时只执行最后一次的调用:

function delayExec(cb) {

}

const test = delayExec(() => console.log(1))

// 只打印一次
setTimeout(() => {
    test()
    test()
}, 1000)

我想了会只想出这种:

function delayExec(cb) {
    let fn
    Promise.resolve().then(() => fn && fn())
    return () => {
        fn = cb
    }
}

但是这样必须在得到test后立即执行,中间不能有非同步的代码

虽然想要的效果和只执行第一次是一样的,但是就是想知道这种能不能实现

阅读 6.2k
3 个回答

如果是只运行每次同步的第一次可以这么写

function delayExec(cb) {
    let p = null;
    return function(...args) {
        if(!p){
            p = Promise.resolve()
            p.then(function(){
                p = null
                cb && cb(...args)
            })
        }
    }
}

let test = delayExec((a) => console.log(a))

setTimeout(() => {
    test(1)
    test(2)
    test(3)
}, 10)
setTimeout(() => {
    test(1)
    test(2)
    test(3)
}, 1000)

// 1
// 1

因为最终运行的都是cb,那么第几次的区别就是参数,所以可以把参数存下来

function delayExec(fn) {
    let p = null, _args;
    return function(...args) {
        _args = args
        if(!p){
            p = Promise.resolve()
            p.then(function(){
                p = null
                fn && fn(..._args)
            })
        }
    }
}

let test = delayExec((a) => console.log(a))

setTimeout(() => {
    test(1)
    test(2)
    test(3)
}, 10)
setTimeout(() => {
    test(1)
    test(2)
    test(3)
}, 1000)

// 3
// 3

听起来你是要做防抖吧?

function debounce(fn, delay=500) {
    let timer = null;
    return function(...args) {
        clearTimeout(timer);
        timer = setTimeout(() => fn.apply(this, args), delay)
    }

}
function debounce(fn, delay=500) {
    let p = Promise.resolve();
    let cb = null;
    return function(...args) {
        cb = fn.bind(this, ...args);
        p.then(() => {
            cb && cb();
            cb = null;
        })
    }

}

“最后一次”指的是,在某个时间节点之前的最近一次,所以问题缺少一个关键的描述:你选择的是哪个时间节点。
比如某人和他父亲的最后一次见面。如果这两个人有一方去世了,那么这最后一次指的是去世前的最近一次,时间节点是去世;如果两人都还健在,那么最后一次指的是从描述此事件的时刻往前回溯的上一次,时间节点是说这个事的时刻。
你的 delayExec 大致指的是上一个事件循环结束前的最后一次,但是你的实现可能有问题,因为每个 Promise 都会添加一个微任务,并在下一个事件循环中依次执行。虽然得以执行的确实是最后一次传入的函数,但是这个函数被执行了许多次。
所以你至少还需要一个标志,避免重复执行:

function delayExec(cb) {
    let fn;
    let executedInThisLoop = false;
    Promise.resolve().then(() => {
      if(executedInThisLoop ) return;
      fn && fn();
      executedInThisLoop = true;
    })
    return () => {
        fn = cb
    }
}

Emmm,这个问题似乎有点哲学和抠字眼的意味。

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题