在JavaScript中等待、延迟、睡眠、暂停

  • A+
所属分类:JQUERY API

许多编程语言都有一个

1
sleep

函数,该函数将程序的执行延迟给定的秒数。但是,由于JavaScript的异步特性,JavaScript没有此功能。在本文中,我们将简要介绍为什么会这样,然后我们将如何实现

1
sleep

发挥自己的作用。

我们从零开始重写了这个流行的指南,以提供最好和最最新的建议。这篇文章于2019年11月更新。

理解JavaScript的执行模型

在我们开始之前,确保正确理解JavaScript的执行模型是很重要的。

考虑以下Ruby代码:

1
2
3
4
5
6
7
8
require 'net/http'
require 'json'

url = 'https://api.github.com/users/jameshibbard'
uri = URI(url)
response = JSON.parse(Net::HTTP.get(uri))
puts response['public_repos']
puts "Hello!"

正如人们可能预期的那样,这段代码向GitHubAPI发出请求,以获取我的用户数据。然后分析响应,输出归我的GitHub帐户的公共回复的数量,最后打印“Hello!”到屏幕上。执行从上到下。

将此与等效的JavaScript版本进行对比:

1
2
3
4
fetch('https://api.github.com/users/jameshibbard')
  .then(res => res.json())
  .then(json => console.log(json.public_repos));
console.log("Hello!");

如果运行此代码,它将输出“Hello!”到屏幕上,然后由我的GitHub帐户引起的公开回购的数量。

这是因为从API中获取数据是JavaScript中的异步操作。JavaScript解释器将遇到

1
fetch

命令和发送请求。它会然而,等待请求完成。相反,它将继续前进,输出“Hello!”到控制台时,当请求在几百毫秒后返回时,它将输出repos的数量。

如果这对你来说是个新闻,你应该看看这个精彩的会议演讲:事件循环到底是什么?.

你可能真的不需要睡眠功能

现在我们已经更好地理解了JavaScript的执行模型,让我们看看JavaScript是如何处理延迟和异步操作的。

使用

1
setTimeout

在JavaScript中创建延迟的标准方法是使用

1
setTimeout

方法。例如:

1
2
console.log("Hello");
setTimeout(() => { console.log("World!"); }, 2000);

这将记录“Hello”到控制台,然后在两秒钟后“World!”在很多情况下,这就足够了:做点什么,等等,然后做一些其他的事情。分类了!

不过,请注意

1
setTimeout

是一种异步方法。尝试修改前面的代码,如下所示:

1
2
3
console.log("Hello");
setTimeout(() => { console.log("World!"); }, 2000);
console.log("Goodbye!");

它将记录:

1
2
3
Hello
Goodbye!
World!

用setTimeout等待事情

它也可以使用

1
setTimeout

(或其表亲)

1
setInterval

)让JavaScript一直等到满足条件为止。例如,下面是如何使用

1
setTimeout

若要等待某个元素出现在网页上,请执行以下操作:

1
2
3
4
5
6
7
8
9
10
11
function pollDOM () {
const el = document.querySelector('my-element');

if (el.length) {
// Do something with el
} else {
setTimeout(pollDOM, 300); // try again in 300 milliseconds
}
}

pollDOM();

这假设元素会在某个时候出现。如果您不确定是否存在这种情况,则需要考虑取消计时器(使用

1
clearTimeout

1
clearInterval

).

如果您想了解更多关于JavaScript的信息

1
setTimeout

方法,请咨询我们的教程有足够的例子让你开始。

现代JavaScript中的流控制

在编写JavaScript时,我们通常需要等待发生一些事情(例如,从API中获取数据),然后进行响应(例如更新UI以显示数据)。

上面的示例为此使用了一个匿名回调函数,但是如果您需要等待多件事情的发生,那么语法很快就会变得非常糟糕,最后您将在回调地狱.

幸运的是,这种语言在过去的几年里有了很大的发展,现在为我们提供了新的结构来避免这种情况。

例如,使用异步等待我们可以重写初始代码,从GitHubAPI获取信息:

1
2
3
4
5
(async () => {
const res = await fetch(`https://api.github.com/users/jameshibbard`);
const json = await res.json();
console.log(json.public_repos);
console.log("Hello!");

现在,代码从上到下执行。JavaScript解释器等待网络请求完成,并首先记录公共回复的数量,然后是“Hello!”留言。

如果这是你想要完成的事情,我鼓励你读我们的文章现代JS中的流控制:异步/等待承诺的回调.

为原生JavaScript带来睡眠

如果您仍然支持我,那么我想您已经做好了阻止执行线程并让JavaScript等待它的准备。

你可以这样做:

1
2
3
4
5
6
7
8
9
10
11
function sleep(milliseconds) {
const date = Date.now();
let currentDate = null;
do {
currentDate = Date.now();
} while (currentDate - date < milliseconds);
}

console.log("Hello");
sleep(2000);
console.log("World!");

正如预期的那样,这将记录“Hello”,暂停两秒钟,然后记录“World!”

它通过使用Date.now方法获取自1970年1月1日以来已过的毫秒数,并将该值分配给

1
date

变量。然后创建一个空的

1
currentDate

变量之前,在输入

1
do ... while

循环。在循环中,它反复获取自1970年1月1日以来经过的毫秒数,并将该值赋值给先前声明的

1
currentDate

变量。循环将继续进行,而两者之间的区别是

1
date

1
currentDate

小于所需的延迟(以毫秒为单位)。

工作完成了,对吧?嗯,不完全是…

更好的睡眠功能

也许这段代码完全符合您的期望,但请注意,它有一个很大的缺点:循环将阻止JavaScript的执行线程,并确保在程序完成之前没有人可以与您的程序进行交互。如果你需要一个大的延迟,它甚至有可能会使事情完全崩溃。

那该怎么办呢?

此外,还可以将本文前面学到的技术结合起来,使睡眠功能不那么具有侵扰性:


1
2
3
4
5
6
function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

console.log("Hello");
sleep(2000).then(() => { console.log("World!"); });

这段代码将记录“Hello”,等待两秒钟,然后记录“World!”在引擎盖下面我们使用的是

1
setTimeout

方法来解析承诺在给定的毫秒数之后。

注意,我们需要使用

1
然后

回调,以确保第二条消息被延迟记录。我们还可以将更多的回调链接到第一个:


1
2
3
4
5
6
7
console.log("Hello");
sleep(2000)
  .then(() => { console.log("World!"); })
  .then(() => {
    sleep(2000)
      .then(() => { console.log("Goodbye!"); })
    });

这个很管用,但看起来很难看。我们可以用

1
async ... await

:

1
2
3
4
5
6
7
8
9
10
11
12
13
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}

async function delayedGreeting() {
console.log("Hello");
await sleep(2000);
console.log("World!");
await sleep(2000);
console.log("Goodbye!");
}

delayedGreeting();

这看起来更好,但意味着任何代码都使用

1
sleep

函数需要标记为

1
async

.

当然,这两种方法都有它们所做的缺点(或特性)。暂停整个程序的执行。只有你的功能睡觉:


1
2
3
4
5
6
7
8
9
10
11
12
function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

async function delayedGreeting() {
  console.log("Hello");
  await sleep(2000);
  console.log("World!");
}

delayedGreeting();
console.log("Goodbye!");

上面的代码记录如下:


1
2
3
Hello
Goodbye!
World!

 

avatar

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: