如何使JavaScript休眠或等待
来源:公众号《前端全栈开发者》
JavaScript不具有
sleep()
函数,该函数会导致代码在恢复执行之前等待指定的时间段。如果需要JavaScript等待,该怎么做呢?
假设您想将三则消息记录到Javascript控制台,每条消息之间要延迟一秒钟。JavaScript中没有
sleep()
方法,所以你可以尝试使用下一个最好的方法
setTimeout()
。
不幸的是,
setTimeout()
不能像你期望的那样正常工作,这取决于你如何使用它。你可能已经在JavaScript循环中的某个点上试过了,看到
setTimeout()
似乎根本不起作用。
问题的产生是由于将
setTimeout()
误解为
sleep()
函数,而实际上它是按照自己的一套规则工作的。
在本文中,我将解释如何使用
setTimeout()
,包括如何使用它来制作一个睡眠函数,使JavaScript暂停执行并在连续的代码行之间等待。
浏览一下
setTimeout()
的文档,它似乎需要一个 "延迟 "参数,以毫秒为单位。
回到原始问题,您尝试调用
setTimeout(1000)
在两次调用
console.log()
函数之间等待1秒。
不幸的是
setTimeout()
不能这样工作:
setTimeout(1000)
console.log(1)
setTimeout(1000)
console.log(2)
setTimeout(1000)
console.log(3)
for (let i = 0; i <= 3; i++) {
setTimeout(1000)
console.log(`#${i}`)
这段代码的结果完全没有延迟,就像
setTimeout()
不存在一样。
回顾文档,你会发现问题在于实际上第一个参数应该是函数调用,而不是延迟。毕竟,
setTimeout()
实际上不是
sleep()
方法。
你重写代码以将回调函数作为第一个参数并将必需的延迟作为第二个参数:
setTimeout(() => console.log(1), 1000)
setTimeout(() => console.log(2), 1000)
setTimeout(() => console.log(3), 1000)
for (let i = 0; i <= 3; i++) {
setTimeout(() => console.log(`#${i}`), 1000)
这样一来,三个console.log的日志信息在经过1000ms(1秒)的单次延时后,会一起显示,而不是每次重复调用之间延时1秒的理想效果。
在讨论如何解决此问题之前,让我们更详细地研究一下
setTimeout()
函数。
检查setTimeout ()
你可能已经注意到上面第二个代码片段中使用了箭头函数。这些是必需的,因为你需要将匿名回调函数传递给
setTimeout()
,该函数将在超时后运行要执行的代码。
在匿名函数中,你可以指定在超时时间后执行的任意代码:
// 使用箭头语法的匿名回调函数。
setTimeout(() => console.log("你好!"), 1000)
// 这等同于使用function关键字
setTimeout(function() { console.log("你好!") }, 1000)
理论上,你可以只传递函数作为第一个参数,回调函数的参数作为剩余的参数,但对我来说,这似乎从来没有正确的工作:
// 应该能用,但不能用
setTimeout(console.log, 1000, "你好")
人们使用字符串解决此问题,但是不建议这样做。从字符串执行JavaScript具有安全隐患,因为任何不当行为者都可以运行作为字符串注入的任意代码。
// 应该没用,但确实有用
setTimeout(`console.log("你好")`, 1000)
那么,为什么在我们的第一组代码示例中
setTimeout()
失败?好像我们在正确使用它,每次都重复了1000ms的延迟。
原因是
setTimeout()
作为同步代码执行,并且对
setTimeout()
的多次调用均同时运行。每次调用
setTimeout()
都会创建异步代码,该代码将在给定延迟后稍后执行。由于代码段中的每个延迟都是相同的(1000毫秒),因此所有排队的代码将在1秒钟的单个延迟后同时运行。
如前所述,
setTimeout()
实际上不是
sleep()
函数,取而代之的是,它只是将异步代码排入队列以供以后执行。幸运的是,可以使用
setTimeout()
在JavaScript中创建自己的
sleep()
函数。
如何编写sleep函数
通过Promises,
async
和
await
的功能,您可以编写一个
sleep()
函数,该函数将按预期运行。
但是,你只能从
async
函数中调用此自定义
sleep()
函数,并且需要将其与
await
关键字一起使用。
这段代码演示了如何编写一个
sleep()
函数:
const sleep = (delay) => new Promise((resolve) => setTimeout(resolve, delay))
const repeatedGreetings = async () => {
await sleep(1000)
console.log(1)
await sleep(1000)
console.log(2)
await sleep(1000)
console.log(3)
repeatedGreetings()
此JavaScript
sleep()
函数的功能与您预期的完全一样,因为
await
导致代码的同步执行暂停,直到Promise被解决为止。
一个简单的选择
另外,你可以在第一次调用
setTimeout()
时指定增加的超时时间。
以下代码等效于上一个示例:
setTimeout(() => console.log(1), 1000)
setTimeout(() => console.log(2), 2000)
setTimeout(() => console.log(3), 3000)
使用增加超时是可行的,因为代码是同时执行的,所以指定的回调函数将在同步代码执行的1、2和3秒后执行。
它会循环运行吗?
如你所料,以上两种暂停JavaScript执行的选项都可以在循环中正常工作。让我们看两个简单的例子。
这是使用自定义
sleep()
函数的代码段:
const sleep = (delay) => new Promise((resolve) => setTimeout(resolve, delay))
async function repeatGreetingsLoop() {
for (let i = 0; i <= 5; i++) {
await sleep(1000)
console.log(`Hello #${i}`)