第一次立即执行setInterval函数

有一种方法可以配置javascript的setInterval方法来立即执行该方法,然后与计时器一起执行

261993 次浏览

最简单的方法是自己第一次直接调用函数:

foo();
setInterval(foo, delay);

然而,有很好的理由避免setInterval——特别是在某些情况下,整个setInterval事件负载可以在彼此之后立即到达,没有任何延迟。另一个原因是,如果你想停止循环,你必须显式调用clearInterval,这意味着你必须记住从最初的setInterval调用返回的句柄。

因此,另一种方法是让foo在后续调用中使用setTimeout来触发自身:

function foo() {
// do stuff
// ...


// and schedule a repeat
setTimeout(foo, delay);
}


// start the cycle
foo();

这保证了调用之间存在至少delay间隔。如果需要的话,它也使取消循环变得更容易——当达到循环终止条件时,你只是不调用setTimeout

更好的是,你可以把这一切都包装在立即调用的函数表达式中,它创建了函数,然后像上面一样再次调用自己,并自动开始循环:

(function foo() {
...
setTimeout(foo, delay);
})();

它定义了函数,并开始了整个循环。

我不确定我是否理解正确,但你可以很容易地做这样的事情:

setInterval(function hello() {
console.log('world');
return hello;
}(), 5000);

显然有很多方法可以做到这一点,但这是我能想到的最简洁的方法。

你可以将setInterval()包装在提供该行为的函数中:

function instantGratification( fn, delay ) {
fn();
setInterval( fn, delay );
}

...然后这样使用它:

instantGratification( function() {
console.log( 'invoked' );
}, 3000);

函数的即时异步调用存在一个问题,因为标准的setTimeout/setInterval即使直接将其设置为0,也具有大约几毫秒的最小超时。这是由浏览器特定的工作引起的。

一个具有REAL零延迟的代码示例,可在Chrome, Safari, Opera中工作

function setZeroTimeout(callback) {
var channel = new MessageChannel();
channel.port1.onmessage = callback;
channel.port2.postMessage('');
}

你可以找到更多的信息在这里

在第一次手动调用之后,您可以用函数创建一个间隔。

如果你需要,这里有一个包装来美化它:

(function() {
var originalSetInterval = window.setInterval;


window.setInterval = function(fn, delay, runImmediately) {
if(runImmediately) fn();
return originalSetInterval(fn, delay);
};
})();

将setInterval的第三个参数设置为true,它将在调用setInterval后立即运行:

setInterval(function() { console.log("hello world"); }, 5000, true);

或者省略第三个参数,它将保持原来的行为:

setInterval(function() { console.log("hello world"); }, 5000);

一些浏览器支持setInterval的附加参数,这个包装器没有考虑到这一点;我认为这些很少使用,但如果你确实需要它们,请记住这一点。

其实最快的就是去做

interval = setInterval(myFunction(),45000)

它会调用myfunction,然后每45秒再做一次,这和之前做的不一样

interval = setInterval(myfunction, 45000)

它不会调用它,但只安排它

为了解决这个问题,我在页面加载后第一次运行这个函数。

function foo(){ ... }


window.onload = function() {
foo();
};


window.setInterval(function()
{
foo();
}, 5000);

// YCombinator
function anonymous(fnc) {
return function() {
fnc.apply(fnc, arguments);
return fnc;
}
}


// Invoking the first time:
setInterval(anonymous(function() {
console.log("bar");
})(), 4000);


// Not invoking the first time:
setInterval(anonymous(function() {
console.log("foo");
}), 4000);
// Or simple:
setInterval(function() {
console.log("baz");
}, 4000);

好吧,这太复杂了,让我说得简单点:

function hello(status ) {
console.log('world', ++status.count);
  

return status;
}


setInterval(hello, 5 * 1000, hello({ count: 0 }));

由于同样的问题,我偶然发现了这个问题,但如果你需要像setInterval()一样行为完全,但与只有不同的是,函数在开始时立即被调用,那么这些答案都没有帮助。

以下是我对这个问题的解决方案:

function setIntervalImmediately(func, interval) {
func();
return setInterval(func, interval);
}

该解决方案的优点:

  • 使用setInterval的现有代码可以很容易地通过替换来适应
  • 在严格模式下工作
  • 它使用现有的命名函数和闭包
  • 你仍然可以使用返回值并稍后将其传递给clearInterval()

例子:

// create 1 second interval with immediate execution
var myInterval = setIntervalImmediately( _ => {
console.log('hello');
}, 1000);


// clear interval after 4.5 seconds
setTimeout( _ => {
clearInterval(myInterval);
}, 4500);

坦率地说,如果你真的需要使用setInterval,那么你也可以替换原来的setInterval。因此,在现有代码之前添加此代码时不需要更改代码:

var setIntervalOrig = setInterval;


setInterval = function(func, interval) {
func();
return setIntervalOrig(func, interval);
}

尽管如此,上面列出的所有优点都适用于这里,但没有必要进行替换。

我建议按以下顺序调用这些函数

var _timer = setInterval(foo, delay, params);
foo(params)

如果你想在特定条件下clearInterval(_timer),你也可以将_timer传递给foo

var _timer = setInterval(function() { foo(_timer, params) }, delay);
foo(_timer, params);

有一个方便的npm包叫做firstInterval(完全披露,它是我的)。

这里的许多示例不包括参数处理,并且在任何大型项目中更改setInterval的默认行为都是邪恶的。从文档中可以看出:

这种模式

setInterval(callback, 1000, p1, p2);
callback(p1, p2);

等于

firstInterval(callback, 1000, p1, p2);

如果你是老式的浏览器,不想要依赖,它是一个简单的从代码。剪切粘贴

这里有一个简单的版本给新手,没有所有的混乱。它只是声明函数,调用它,然后开始interval。就是这样。

//Declare your function here
function My_Function(){
console.log("foo");
}


//Call the function first
My_Function();


//Set the interval
var interval = window.setInterval( My_Function, 500 );

因为有人需要把外部的this带入内部,就像它是一个箭头函数一样。

(function f() {
this.emit("...");
setTimeout(f.bind(this), 1000);
}).bind(this)();

如果上面产生的垃圾困扰着你,你可以做一个闭包。

(that => {
(function f() {
that.emit("...");
setTimeout(f, 1000);
})();
})(this);

或者根据你的代码考虑使用@autobind装饰器

你可以在函数中设置一个非常小的初始延迟时间(例如100),并将其设置为你想要的延迟时间:

var delay = 100;


function foo() {
console.log("Change initial delay-time to what you want.");
delay = 12000;
setTimeout(foo, delay);
}

如果你可以使用RxJS,有一个叫做timer()的东西:

import { Subscription, timer } from 'rxjs';


const INITIAL_DELAY = 1;
const INTERVAL_DELAY = 10000;
const timerSubscription = timer(INITIAL_DELAY, INTERVAL_DELAY)
.subscribe(() => {
this.updateSomething();
});


// when destroying
timerSubscription.unsubscribe();

这个例子建立在@Alnitak的回答上,但是使用await Promise在循环循环中进行更细粒度的控制。

比较的例子:

let stillGoing = true;


(function foo() {
console.log('The quick brown fox did its thing');
if (stillGoing) setTimeout(foo, 5000);
})();


foo();

在上面的例子中,我们调用foo(),然后它每5秒调用一次自己。

但是如果,在未来的某个时候,我们为了停止循环而将stillGoing设为false,即使在发出停止命令之后,我们仍然会得到一个额外的log行。这是因为在任何给定的时间,在我们将stillGoing设置为false之前,当前迭代将已经创建了一个超时来调用下一次迭代。

如果我们使用await Promise作为延迟机制,那么我们就有机会在调用下一次迭代之前停止循环:

let stillGoing = true;


(async function foo() {
console.log('The quick brown fox did its thing');
await new Promise(resolve => setTimeout(resolve, 5000));
if (stillGoing) foo();
})();


foo();

在第二个例子中,我们从设置5000ms的延迟开始,我们检查stillGoing的值,并决定是否调用另一个递归是合适的。

如果我们在任意点将stillGoing设为false,在我们设置值之后就不会打印出额外的log行。

需要注意的是,这要求函数是异步的,对于给定的使用,这可能是也可能不是一个选项。

对于那些使用React的人,下面是我解决这个问题的方法:

const intervalRef = useRef(0);


useEffect(() => {
if (condition is true){
if (intervalRef.current === 0) {
callMyFunction();
}
const interval = setInterval(() => {
callMyFunction();
}, 5_000);
intervalRef.current = interval;
} else {
clearInterval(intervalRef.current);
}
}, [deps]);