乐趣区

关于前端:JavaScript怎么模拟-delaysleeppausewait-方法

首发于公众号 大迁世界,欢送关注。📝 每周 7 篇实用的前端文章 🛠️ 分享值得关注的开发工具 😜分享集体守业过程中的趣事

许多编程语言都有一个 sleep 函数,能够提早程序的执行若干秒。JavaScript 短少这个内置性能,但不必放心。在这篇文章中,咱们将探讨在 JavaScript 代码中实现提早的各种技巧,同时思考到该语言的异步性质。

如何在 JS 中创立 sleep 函数

对于那些只想疾速解决问题而不想深刻理解技术细节的人,咱们也有简单明了的解决方案。上面是如何在你的 JavaScript 工具箱中增加一个 sleep 函数的最间接形式:

function sleep(ms) {return new Promise(resolve => setTimeout(resolve, ms));
}

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

运行这段代码,你会在控制台看到“Hello”。而后,在短暂的两秒钟后,“World!”v 会接着呈现。这是一种既简洁又无效的引入提早的办法。

如果你只是为了这个来的,那太好了!但如果你对“为什么”和“怎么做”的起因感到好奇,还有更多能够学习的内容。JavaScript 中解决工夫有其轻微之处,理解这些可能会对你有所帮忙。

了解 JavaScript 的执行模型

当初咱们曾经有了一个疾速的解决方案,让咱们深刻理解 JavaScript 的执行模型的机制。了解这一点对于无效地治理代码中的工夫和异步操作至关重要。

思考以下 Ruby 代码:

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!'

正如人们所冀望的,这段代码向 GitHub API 发送一个申请以获取我的用户数据。而后解析响应,输入与我的 GitHub 帐户关联的公共仓库的数量,最初在屏幕上打印“Hello!”。执行是从上到下进行的。

相比之下,这是雷同性能的 JavaScript 版本:

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

如果你运行这段代码,它会先在屏幕上输入“Hello!”,而后输入与我的 GitHub 帐户关联的公共仓库的数量。

这是因为在 JavaScript 中,从 API 获取数据是一个异步操作。JavaScript 解释器会遇到 fetch 命令并发送申请。然而,它不会期待申请实现。相同,它会继续执行,将“Hello!”输入到控制台,而后当申请在几百毫秒后返回时,它会输入仓库的数量。

如何在 JavaScript 中正确应用 SetTimeout

既然咱们曾经更好地了解了 JavaScript 的执行模型,让咱们看看 JavaScript 是如何解决提早和异步代码的。

在 JavaScript 中创立提早的规范办法是应用其 setTimeout 办法。例如:

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

这将在管制台上输入 “Hello”,而后两秒后输入 “World!”。在很多状况下,这曾经足够了:做某事,而后在短暂的提早后,做其余事件。问题解决!

但可怜的是,事件并不总是那么简略。

你可能会认为 setTimeout 会暂停整个程序,但事实并非如此。它是一个异步函数,这意味着其余的代码不会期待它实现。

例如,假如你运行了以下代码:

console.log('Hello');
setTimeout(() => { console.log('World!'); }, 2000);
console.log('Goodbye!');

你会看到以下输入:

Hello
Goodbye!
World!

留神“Goodbye!”是如何呈现在“World!”之前的?这是因为 setTimeout 不会阻塞其余代码的执行。

这意味着你不能这样做:

console.log('Hello');
setTimeout(1000);
console.log('World');

“Hello” 和 “World” 会立刻被记录在管制台上,之间没有显著的提早。

你也不能这样做:

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

花一秒钟考虑一下下面的代码片段可能会产生什么。

它不会在每个数字之间提早一秒钟打印数字 0 到 4。相同,你实际上会失去五个 4,它们在四秒后一次性全副打印进去。为什么呢?因为循环不会暂停执行。它不会期待 setTimeout 实现才进入下一次迭代。

那么 setTimeout 实际上有什么用呢?当初让咱们来看看。

setTimeout() 函数的检查和最佳实际

正如你能够在咱们的 setTimeout 教程中浏览到的,原生 JavaScript setTimeout 函数在指定的提早(以毫秒为单位)后调用一个函数或执行一个代码片段。

这可能在某些状况下是有用的,例如,如果你心愿在访问者浏览你的页面一段时间后显示一个弹出窗口,或者你心愿在从元素上移除悬停成果之前有短暂的提早(以防用户意外地鼠标移出)。

setTimeout 办法承受一个函数的援用作为第一个参数。

这能够是函数的名称:

function greet(){alert('Howdy!');
}
setTimeout(greet, 2000);

它能够是一个指向函数的变量(函数表达式):

const greet = function(){alert('Howdy!');
};
setTimeout(greet, 2000);

或者它能够是一个匿名函数(在这种状况下是箭头函数):

setTimeout(() => { alert('Howdy!'); }, 2000);

也能够将一段代码字符串传递给 setTimeout 以供其执行:

然而,这种办法是不可取的,因为:

  1. 它很难浏览(因而很难保护和 / 或调试)
  2. 它应用了一个隐含的 eval,这是一个潜在的平安危险
  3. 它比代替计划慢,因为它必须调用 JS 解释器

如前所述,setTimeout 非常适合在提早后触发一次性操作,但也能够应用 setTimeout(或其表亲 setInterval)来让 JavaScript 期待直到满足某个条件。例如,上面是如何应用 setTimeout 期待某个元素呈现在网页上的形式:

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();

这假如该元素最终会呈现。如果你不确定这是否会产生,你须要思考勾销计时器(应用 clearTimeoutclearInterval)。

在 JS 中应用递增超时作为 Sleep 函数的代替计划

有时,你可能会发现自己想要在一系列操作中引入提早。尽管你能够应用各种办法来模仿一个 Sleep 函数,但还有另一种常常被忽视的办法:递增超时

这个思路很简略:你不是暂停整个执行线程,而是应用 setTimeout 为每个后续操作减少提早。这样,你能够创立一个提早操作的序列,而不会阻塞浏览器或侵害用户体验。

上面是一个疾速示例:

let delay = 1000; // 从 1 秒的提早开始

for (let i = 0; i < 5; i++) {setTimeout(() => {console.log(` 这是音讯 ${i + 1}`);
  }, delay);

  delay += 1000; // 每次迭代提早减少 1 秒
}

在这个示例中,第一条音讯将在 1 秒后呈现,第二条音讯在 2 秒后,依此类推,直到第五条音讯在 5 秒后。

这种办法的长处是它不阻塞,易于实现,并且不须要理解 promisesasync/await。然而,它不适用于须要准确计时或错误处理的简单异步操作

古代 JavaScript 中的流控制

编写 JavaScript 时,咱们常常须要期待某件事情产生(例如,从 API 获取数据),而后做出响应(例如,更新 UI 以显示数据)。

下面的示例应用了一个匿名回调函数来实现这一目标,但如果你须要期待多个事件产生,语法很快就会变得相当简单,你最终会陷入回调天堂。

侥幸的是,这门语言在过来几年里有了很大的倒退,当初为咱们提供了新的结构来防止这一点。

例如,应用 async await,咱们能够重写最后获取 GitHub API 信息的代码:

(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!”音讯。

将 Sleep 函数引入原生 JavaScript

如果你还在看这篇文章,那么我猜你肯定是想阻塞那个执行线程,并让 JavaScript 期待一下。

上面是你可能会这样做的形式:

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 日以来通过的毫秒数,并将该值调配给一个 date 变量。而后它创立一个空的 currentDate 变量,而后进入一个 do ... while 循环。在循环中,它会反复获取自 1970 年 1 月 1 日以来通过的毫秒数,并将该值调配给之前申明的 currentDate 变量。只有 datecurrentDate 之间的差别小于所需的毫秒数的提早,循环就会持续进行。

工作实现了,对吗?好吧,也不齐全是……

如何在 JavaScript 中编写更好的 Sleep 函数

兴许这段代码正是你所冀望的,但请留神,它有一个很大的毛病:循环会阻塞 JavaScript 的执行线程,并确保在它实现之前没有人能与你的程序进行交互。如果你须要很大的提早,甚至有可能会让整个程序解体。

那么应该怎么做呢?

事实上,也能够联合本文后面学到的技巧来制作一个不太侵入性的 sleep 办法:

function sleep(ms) {return new Promise(resolve => setTimeout(resolve, ms));
}

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

这段代码将在管制台上打印“Hello”,期待两秒,而后打印“World!”在底层,咱们应用 setTimeout 办法在给定的毫秒数后解析一个 promise

留神,咱们须要应用一个 then 回调来确保第二条音讯是带有提早的。咱们还能够在第一个回调函数前面链式地增加更多回调函数。

这样做是可行的,但看起来不太好看。咱们能够应用 async ... await 来丑化它:

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();

这看起来更好看,但这意味着应用 sleep 函数的任何代码都须要被标记为 async

当然,这两种办法依然有一个毛病(或特点),那就是它们不会暂停整个程序的执行。只有你的函数会睡眠:

function sleep(ms) {return new Promise(resolve => setTimeout(resolve, ms));
}

async function delayedGreeting() {console.log('Hello');
  await sleep(2000); // await 只暂停以后的异步函数
  console.log('World!');
}

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

下面的代码会顺次打印出:

Hello
Goodbye!
World!

这样,你能够依据须要灵便地应用不同的办法和技术来实现 JavaScript 中的提早和异步操作。

创立 JS Sleep 函数的最佳实际

咱们曾经探讨了各种在 JavaScript 中引入提早的办法。当初让咱们总结一下哪种办法最适宜不同的场景,以及哪种办法通常应该防止。

1. 纯正的 setTimeout

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

👍 长处:容易了解,非阻塞。
👎 毛病:对异步操作的管制无限。
📝 何时应用:实用于简略的、一次性的提早,或根底轮询。

2. 递增的 setTimeout

setTimeout(() => { console.log('Hello'); }, 1000);
setTimeout(() => { console.log('World!'); }, 2000);

👍 长处:非阻塞性,易于实现,不须要理解 promisesasync/await
👎 毛病:不适用于简单的异步操作。没有错误处理。
📝 何时应用:用于有工夫距离的简略序列。

3. 通过循环阻塞事件循环

console.log('Hello');
const date = Date.now();
let currentDate = null;
do {currentDate = Date.now();
} while (currentDate - date < 2000);
console.log('World!');

👍 长处:模拟传统的 sleep 行为。
👎 毛病:阻塞整个线程,可能会解冻 UI 或导致程序解体。
⚠️ 强烈不举荐:只有在你相对须要暂停执行并且意识到其中的危险时才应用。

4. 应用 Promises 与 setTimeout

const sleep = function(ms) {return new Promise(resolve => setTimeout(resolve, ms));
}
console.log('Hello');
sleep(2000).then(() => { console.log('World!'); });

👍 长处:非阻塞性,对异步操作有更多的管制。
👎 毛病:须要了解 promises。更长的 promise 链可能会变得有点凌乱。
📝 何时应用:当你须要更多对工夫和异步操作的管制时。

5. 应用 async/await 与 Promises

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();

👍 长处:语法清晰,易于浏览,非阻塞。
👎 毛病:须要了解 async/await 和 promises。须要在模块内部“包装”函数。
✅ 强烈推荐:这是最古代和洁净的办法,尤其是在解决多个异步操作时。

总结

JavaScript 中的时序问题是许多开发人员头疼的起因,你如何解决它们取决于你想实现什么。

只管在许多其余语言中都有 sleep 函数,但我激励你去承受 JavaScript 的异步个性,尽量不要与这门语言作对。当你习惯了它,它实际上是相当不错的。

交换

首发于公众号 大迁世界,欢送关注。📝 每周一篇实用的前端文章 🛠️ 分享值得关注的开发工具 ❓ 有疑难?我来答复

本文 GitHub https://github.com/qq449245884/xiaozhi 已收录,有一线大厂面试残缺考点、材料以及我的系列文章。

退出移动版