优秀的编程知识分享平台

网站首页 > 技术文章 正文

详解最火的JavaScript休眠或等待功能,最“潮”的程序员非你莫属

nanyue 2024-10-24 11:43:14 技术文章 2 ℃

首先我们先说明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的日志信息在经过时长约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()会失败?好像我们的使用过程也没有出现错误呀,而且每次都是1秒钟的延迟。

其实根本原因在于setTimeout()是作为代码同步运行的,不仅如此,而且对setTimeout() 的多次调用都是同时运行的。我们只要调用一次就会创建一次异步代码,这个代码会在给定延迟之后再运行。因为我们知道每个延迟都是给定的一秒钟,所以排队的代码会在1秒钟延迟后运行。

所以综上所述setTimeout() 并不是sleep() 函数,这个仅仅是将异步代码安排进队列用来保证之后的运行。但是优点在于可以使用 setTimeout() 在JavaScript中创建自己的 sleep() 函数。

如何编写sleep函数

我们可以用Promises,async 和 await的功能编写一个 sleep() 函数,这个函数就可以按照预期来运行。但是弊端在于你只能通过async 函数中调用这个自定义的sleep() 函数,在调用的过程中伴随 await关键字一起使用。举个例子:

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}`) 
    } 
} 
repeatGreetingsLoop() 

这是增加超时的:

for (let i = 0; i <= 5; i++) { 
  setTimeout(() => console.log(`Hello #${i}`), 1000 * i) 
} 

总结

JavaScript也许是没有sleep() 或 wait() 函数,但是内置的setTimeout() 函数是非常容易创建出一个JavaScript。虽然容易,但是使用一定要谨慎。

我们仅仅对其本质来说,setTimeout() 是不能当作 sleep() 函数来用的,但是你使用async 和 await来创建自定义JavaScript sleep()函数是完全可以的。

当我们采用新的方法时,完全可以将增加的超时传递给 setTimeout() 来模拟 sleep() 函数。其可行的原因在于所有对setTimeout() 的调用都是同步执行的。

Tags:

最近发表
标签列表