惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Microsoft Azure Blog
Microsoft Azure Blog
Engineering at Meta
Engineering at Meta
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
F
Fortinet All Blogs
博客园 - 叶小钗
T
Tailwind CSS Blog
Security Archives - TechRepublic
Security Archives - TechRepublic
T
The Exploit Database - CXSecurity.com
Blog — PlanetScale
Blog — PlanetScale
T
Tenable Blog
人人都是产品经理
人人都是产品经理
D
DataBreaches.Net
A
Arctic Wolf
P
Proofpoint News Feed
S
SegmentFault 最新的问题
C
CERT Recently Published Vulnerability Notes
T
Threatpost
Y
Y Combinator Blog
WordPress大学
WordPress大学
L
LINUX DO - 热门话题
酷 壳 – CoolShell
酷 壳 – CoolShell
V
Vulnerabilities – Threatpost
AWS News Blog
AWS News Blog
小众软件
小众软件
U
Unit 42
云风的 BLOG
云风的 BLOG
美团技术团队
S
Securelist
C
Cybersecurity and Infrastructure Security Agency CISA
L
LangChain Blog
G
GRAHAM CLULEY
P
Proofpoint News Feed
I
Intezer
Security Latest
Security Latest
L
Lohrmann on Cybersecurity
NISL@THU
NISL@THU
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
The Hacker News
The Hacker News
Cisco Talos Blog
Cisco Talos Blog
T
The Blog of Author Tim Ferriss
H
Heimdal Security Blog
T
Tor Project blog
MongoDB | Blog
MongoDB | Blog
The Cloudflare Blog
T
Troy Hunt's Blog
Know Your Adversary
Know Your Adversary
博客园 - 司徒正美
Google Online Security Blog
Google Online Security Blog
V
V2EX
Application and Cybersecurity Blog
Application and Cybersecurity Blog

DaraW

GMTC 北京 2021 小程序开发实践专场所见所想 从一次 Bug 定位来看 Taro H5 的组件实现 浅谈 B 站新 BV 号的设计 复盘:Vue.js 是如何成功的 你可能不知道的 Git React 应用性能优化一例 Thrift RPC Mock 方案探索 前端视频质量监控 前端函数式编程 多说一句再见 《你不知道的JS上卷》阅读小记之setTimeout的this指向问题 ES6实现内部类的写法 IntelliJ IDEA在Linux下字体不正常解决方案 node-thunkify源码阅读笔记 如何监听JS变量的变化 JS实现自定义事件 制作列带有斑马条纹背景的表格 git用户名莫名其妙变化及挽救措施 记一次前端性能优化实战
将setTimeout函数队列
DaraW · 2016-02-12 · via DaraW

问题

先来看一段代码:

1
2
3
4
5
6
7
8
9
10
11
setTimeout(function() {
console.log('3000ms');
}, 3000);

console.log('first');

setTimeout(function() {
console.log('1000ms');
}, 1000);

console.log('second');

控制台输出为:

1
2
3
4
first
second
1000ms
3000ms

如果我们想让第一个定时器的回调函数执行完再执行第二个定时器的回调函数该怎么做呢?

分析

JavaScript一开始作为浏览器脚本语言出现,为了避免多线程带来的管理问题,被设计成了单线程,即使是Node.js中的cluster以及Web Worker也并没有改变JS单线程的本质。
单线程意味着阻塞,所有的任务要排队执行,前一个任务未执行完下一个任务是不会执行的。如果有一个任务耗时很长,那么后面的任务也只有干等着。为了解决这个问题,JS内部还有一个消息队列,并采用EventLoop来处理消息队列中的消息。
在上述栗子中,setTimeout会在指定的时间向消息队列中添加一条消息,在消息得到处理的时候也就是回调函数执行的时候,而主线程中的console.log不会等待,所以上述栗子中先按顺序执行了两个console.log,然后才按照定时分别执行1000ms和3000ms时的回调函数。
关于JS的单线程的问题这里不做更多解释,想了解更多的话可以看看下面参考中的阮一峰老师的浏览器的JavaScript引擎,讲的非常易懂。

那么上述问题中的需求就是说,如何做到在第一个定时器的回调函数执行后,第二个定时器才开始定时并等待执行回调函数。
最容易想到了方法就是回调函数。封装定时器,在回调中执行下一个封装函数,但这会导致一个问题,在稍微复杂的情况下,出现“回调地狱”,相信Node.js党应该对回调有着别样的感情。
So,回调函数并不是一个cool的解决方案。

这里的另一个思路就是再构造一个函数队列,封装定时器,给定时器加锁,将其锁在一个函数里,只有确认执行结束才会执行队列中的下一个封装的定时器。

方案

在StackOverflow上有个哥们提了个和我一样的问题,其中一个答案给了一个demo,我这里简化了一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
function recursiveOne(arg1){
if(arg1){
arg1 = false;
setTimeout(function(){recursiveOne(arg1);}, 3000);
}else{
console.log("func1 complete");
coreFunction();
}
}

function recursiveTwo(arg1){
if(arg1){
arg1 = false;
setTimeout(function(){recursiveTwo(arg1);}, 2000);
}else{
console.log("func2 complete");
coreFunction();
}
}

function recursiveThree(arg1){
if(arg1){
arg1 = false;
setTimeout(function(){recursiveThree(arg1);}, 1000);
}else{
console.log("func3 complete");
coreFunction();
}
}

var funcSet = [recursiveOne, recursiveTwo, recursiveThree];
var funcArgs = [[true], [true], [true]];

function coreFunction(){
if(funcSet.length){
var func = funcSet.shift();
func.apply(global, funcArgs.shift())
}
}
coreFunction();

免去了层层回调,优雅的解决的问题:

1
2
3
fun1 complete
fun2 complete
fun3 complete

那么在实际开发环境如何可控的操作执行队列和锁呢?
构造一个入队函数,和出队执行函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var funcSet = [],
funcArgs = [],
funcCont = [],
isRunning = false;

function enQueue(fn, context, args) {
funcSet.push(fn);
funcCont.push(context);
funcArgs.push(args);

if (!isRunning) {
isRunning = true;
coreFunction();
}
}

function coreFunction(){
if(funcSet.length){
funcSet.shift().apply(funcCont.shift(), [].concat(funcArgs.shift()));
}
}

完整的代码实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
function recursiveOne(arg1){
if(arg1){
arg1 = false;
setTimeout(function(){recursiveOne(arg1);}, 3000);
}else{
console.log("func1 complete");
coreFunction();
}
}

function recursiveTwo(arg1){
if(arg1){
arg1 = false;
setTimeout(function(){recursiveTwo(arg1);}, 2000);
}else{
console.log("func2 complete");
coreFunction();
}
}

function recursiveThree(arg1){
if(arg1){
arg1 = false;
setTimeout(function(){recursiveThree(arg1);}, 1000);
}else{
console.log("func3 complete");
coreFunction();
}
}

var funcSet = [],
funcArgs = [],
funcCont = [],
isRunning = false;

function enQueue(fn, context, args) {
funcSet.push(fn);
funcCont.push(context);
funcArgs.push(args);

if (!isRunning) {
isRunning = true;
coreFunction();
}
}

function coreFunction(){
if(funcSet.length){
funcSet.shift().apply(funcCont.shift(), [].concat(funcArgs.shift()));
}
}

enQueue(recursiveOne, this, true);
enQueue(recursiveTwo, this, true);
enQueue(recursiveThree, this, true);

参考