跃迁引擎

空気を読んだ雨降らないでよ

iOS Research & Development


HarmonyOS - Event Loop事件循环、宏任务and微任务

在阅读本文前先给大家抛出几个问题:

  1. 事件循环是什么?
  2. 宏任务和微任务是什么?
  3. 哪些代码触发宏任务,哪些代码触发微任务?

前言

JS是一门单线程语言,单线程就意味着,所有的任务需要排队,前一个任务结束,才会执行下一个任务。

这样所导致的问题是:如果JS执行的时间过长,这样就会造成页面的渲染不连贯,导致页面渲染加载阻塞的感觉。为了解决这个问题,JS中出现了同步和异步。

他们的本质区别是:一条流水线上各个流程的执行顺序不同。

Event Loop

  • Event Loop 实际就是 JavaScript 异步执行机制的一种实现方式;
  • 程序按照主线程-微任务-宏任务的顺序不断重复执行, 并始终维护各执行队列直至全部队列清空的操作就是 Event Loop;

宏任务&微任务

JS 中的任务分为同步与异步

同步任务:即主线程上的任务,按照顺序由上⾄下依次执⾏,当前⼀个任务执⾏完毕后,才能执⾏下⼀个任务。

异步任务:不进⼊主线程,⽽是进⼊任务队列的任务,执行完毕之后会产生一个回调函数,并且通知主线程。当主线程上的任务执行完后,就会调取最早通知自己的回调函数,使其进入主线程中执行。

其中异步任务又分为两种:

  • 宏任务(Macro-take)
  • 微任务(Micro-take)

img

在JavaScript中,微任务的优先级高于宏任务。也就是说,当主线程空闲时,如果有微任务存在,它们会被优先执行,直到微任务队列为空。然后事件循环才会从宏任务队列中取出下一个任务执行。

总之,宏任务和微任务是用于描述任务队列中的不同类型任务的概念,了解它们的原理和执行顺序可以帮助我们更好地理解JavaScript代码的执行过程,并优化代码的性能。

执行顺序

同步任务 > 微任务 > 宏任务

注意:

Promise本身与new Promise()并不是微任务,而是同步任务

首先遇到 new Promise() 会同步执行代码,立即执行参数传入的 executor 函数,executor 函数内部从上到下同步执行代码,遇到 resolve() 或 reject() 时,将值传递给 then() 参数中的回调,并立即将该回调注册到微任务队列。(⚠️注意:resolve() / reject() 的执行也是同步的,它不会等异步完成后执行,除非它在异步回调内,参照示例代码理解)

对于 then() 方法的链式调用而言:尽管调用在形式上是连续的,但是注册回调并不是连续的,下一个 then() 回调注册需要等待上一个 then() 方法执行完毕后才进行注册。

遇到 await,将后续代码放入微任务队列等待执行,交还执行权(跳出该函数体)

任务归属

宏任务:

  • 整体 script 代码(script 代码是异步代码, 其内部可能包含同步代码, 但整体上是异步宏任务)
  • setTimeout
  • setInterval
  • setImmediate(Node.js)

微任务:

  • 原生 Promise 的 then() , catch() 方法
  • await 暂停处的后续语句,将后续代码放入微任务队列等待执行,交还执行权(跳出该函数体)
  • MutationObserver(H5)
  • process.nextTick(Node.js)

EventLoop流转示意图

img

Event table

是一种数据结构,其中存储了一些延迟事件的列表。

1
2
3
setTimeout(function() {
console.log(“This will print after 1 second”);
}, 1000);

以上代码事件发生后(即 1 秒结束后),事件表会将事件发送到事件队列。在上述情况下,1000ms 后,事件表会将要执行的函数发送到EventQueue

Event Queue

是一种使用 FIFO(先进先出)策略存储事件列表的数据结构。

示例代码分析

Demo1

1
2
3
4
5
6
7
8
9
10
11
12
13
console.log('demo: script start');

setTimeout( () => {
console.log('demo: setTimeout');
}, 0);

Promise.resolve().then( () => {
console.log('demo: promise1');
}).then( () => {
console.log('demo: promise2');
});

console.log('demo: script end');

Demo2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
setTimeout(()=>{
console.log('demo: setTimeout1');
}, 0);

let p = new Promise<void>( (resolve, reject) => {
console.log('demo: Promise1');
resolve();
})

p.then( ()=> {
console.log('demo: Promise2');
})

console.log('demo: Promise3');

Demo3

1
2
3
4
5
6
7
8
9
10
11
12
13
Promise.resolve().then( ()=>{
console.log('demo: Promise1')
setTimeout(()=>{
console.log('demo: setTimeout2')
},0)
});

setTimeout( ()=>{
console.log('demo: setTimeout1')
Promise.resolve().then( ()=>{
console.log('demo: Promise2')
})
},0)

测试

测试1

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
console.log('test: 1');

setTimeout( () => {
console.log('test: 2');
let async1 = async () => {
console.log('test: 3');
}
async1()
new Promise<void>( (resolve) => {
console.log('test: 4');
resolve();
}).then( () => {
console.log('test: 5')
})
})

let async2 = async () => {
console.log('test: 6');
}

async2()

new Promise<void>( (resolve) => {
console.log('test: 7');
resolve();
}).then( () => {
console.log('test: 8')
})

setTimeout( () => {
console.log('test: 9');
let async3 = async () => {
console.log('test: 10');
}
async3()
new Promise<void>( (resolve) => {
console.log('test: 11');
resolve();
}).then( () => {
console.log('test: 12')
})
})

以上代码输出什么呢?

测试2

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
let async1 = async () => {
console.log('test: async1 1');
await async2();

console.log('test: async1 2');
}

let async2 = (() => {
console.log('test: async2 3');
})

console.log('test: script start 4 ');
setTimeout(() => {
console.log('test: setTimeout 5 ');
})
async1();
正确: 4 1 3 6 9 2 7 8 5

岳帅: 4 1 3 2 6 9 7 8 5

:: 4 1 3 6 9 2 7 8 1 5
new Promise<void>( (resolve) => {
console.log('test: promise 6');
resolve();
}).then(() => {
console.log('test: promise 7');
return new Promise<void>( (resolve) => {
resolve();
})
}).then((res) => {
console.log('test: promise 8');
})

console.log('test: script end 9');

以上代码输出什么呢?

宏任务、微任务与线程

JS的同步任务,异步任务,跟线程没有什么关系, 哪个线程执行的代码,就在哪个线程返回执行, 宏任务也一样

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
test3() {
console.log(`demo: 开始执行异步方法 执行时当前线程: ${process.tid},当前进程:${process.pid}`);

taskpool.execute(asyncRunAction)

console.log(`demo: 结束执行异步方法 执行时当前线程: ${process.tid},当前进程:${process.pid}`);
}


@Concurrent
export async function asyncRunAction(): Promise<void> {
console.log(`demo: 1 执行时当前线程: ${process.tid},当前进程:${process.pid}`);
return await new Promise<void>( (resolve, reject) => {
setTimeout(()=>{
console.log(`demo: setTimeout1 执行时当前线程: ${process.tid},当前进程:${process.pid}`);
}, 0);

setInterval(()=> {
console.log(`demo: setInterval 执行时当前线程: ${process.tid},当前进程:${process.pid}`);
}, 1000)

let p = new Promise<void>( (resolve, reject) => {
console.log(`demo: Promise1 执行时当前线程: ${process.tid},当前进程:${process.pid}`);
resolve();
})

p.then( ()=> {
console.log(`demo: Promise2 执行时当前线程: ${process.tid},当前进程:${process.pid}`);
})

console.log(`demo: Promise3 执行时当前线程: ${process.tid},当前进程:${process.pid}`);
resolve();
})

}
demo: 开始执行异步方法 执行时当前线程: 6233,当前进程:6233
demo: 结束执行异步方法 执行时当前线程: 6233,当前进程:6233
demo: 1 执行时当前线程: 6259,当前进程:6233
demo: Promise1 执行时当前线程: 6259,当前进程:6233
demo: Promise3 执行时当前线程: 6259,当前进程:6233
demo: Promise2 执行时当前线程: 6259,当前进程:6233
demo: setTimeout1 执行时当前线程: 6259,当前进程:6233
demo: setInterval 执行时当前线程: 6259,当前进程:6233
demo: setInterval 执行时当前线程: 6259,当前进程:6233
demo: setInterval 执行时当前线程: 6259,当前进程:6233
demo: setInterval 执行时当前线程: 6259,当前进程:6233
demo: setInterval 执行时当前线程: 6259,当前进程:6233
...

宏任务与微任务是怎么实现的?

看到上面会不会以为宏任务和微任务 是JS 引擎实现的 ? 其实并不是。

我们双端完整的写过动态化框架,所以知道其实不是这样的。

微任务由JS代码进行调度实现

1
2
3
微任务:
1.原生 Promisethen() , catch() 方法
2.await 暂停处的后续语句

宏任务的实现和调度由外部注入实现

1
2
3
宏任务:
1.setTimeout
2.setInterval

看看 H5的 Window API,就能发现我们熟知的 setTimeoutsetIntervalconsole对象

https://www.runoob.com/jsref/obj-window.html

img

img

所以鸿蒙的语言框架也注入并实现了 Window的这些常规函数,而不是 JS本身就带的能力。

动态化当时写的给 JS引擎注入的函数的代码

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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
/// 注入Global对象
func addGlobalCall() {

// Native内接收JS请求事件的方法名称
let onKTRequestJSCallEventBlock : @convention(block) (String) -> Void = { [weak self]
(jsonString:String) -> Void in

// 调用处理
self?.eventQueue.async {
// 调用增强方法
if let jsCallNative = self?.jsCallNative {
jsCallNative(jsonString)
}
}
}

// Native内接收JS回调事件的方法名称
let onKTResponseJSCallEventBlock : @convention(block) (String) -> Void = { [weak self]
(jsonString:String) -> Void in

// 调用处理
self?.eventQueue.async {
// 调用回复方法
if let jsResponseNative = self?.jsResponseNative {
jsResponseNative(jsonString)
}
}
}

// Timeout函数
let setTimeout : @convention(block) (JSValue?, NSNumber?) -> String = { [weak self]
callback, milliseconds -> String in

guard let self = self else { return "" }

var delay: Double = 0
if let milliseconds = milliseconds {
delay = milliseconds.doubleValue * 0.001
}

var timeId = UUID().uuidString

DispatchQueue.main.async {
let timer = Timer.scheduledTimer(withTimeInterval: delay, repeats: false) { [weak self] (kTimer) in
guard let self = self else {
kTimer.invalidate()
self?.timerStore.removeValue(forKey: timeId)
return
}

kTimer.invalidate()
self.timerStore.removeValue(forKey: timeId)
self.eventQueue.async {
callback?.call(withArguments: [])
}
}
// 存储
self.timerStore[timeId] = timer
}

return timeId
}

// clearTimeout函数
let clearTimeout : @convention(block) (JSValue?) -> Void = { [weak self]
timerId -> Void in

guard let self = self else { return }

var delay: Double = 0
if timerId?.isString ?? false, let timerId = timerId?.toString() as? String {
DispatchQueue.main.async {
if let timer = self.timerStore[timerId] {
timer.invalidate()
self.timerStore.removeValue(forKey: timerId)
}
}
}
}

// setInterval函数
let setInterval : @convention(block) (JSValue?, NSNumber?) -> String = { [weak self]
callback, milliseconds -> String in

guard let self = self else { return "" }

var delay: Double = 0
if let milliseconds = milliseconds {
delay = milliseconds.doubleValue * 0.001
}

var timeId = UUID().uuidString
DispatchQueue.main.async {
let timer = Timer.scheduledTimer(withTimeInterval: delay, repeats: true) { [weak self] (kTimer) in
guard let self = self else {
kTimer.invalidate()
self?.timerStore.removeValue(forKey: timeId)
return
}
self.eventQueue.async {
callback?.call(withArguments: [])
}
}
// 存储
self.timerStore[timeId] = timer

// RunLoop.current.add(timer, forMode: .default)
}

return timeId
}

// clearInterval函数
let clearInterval : @convention(block) (JSValue?) -> Void = { [weak self]
timerId -> Void in

guard let self = self else { return }

var delay: Double = 0
if timerId?.isString ?? false, let timerId = timerId?.toString() as? String {
DispatchQueue.main.async {
if let timer = self.timerStore[timerId] {
timer.invalidate()
self.timerStore.removeValue(forKey: timerId)
}
}
}
}

// btoa函数
let btoa : @convention(block) (String?) -> String = { [weak self]
resString -> String in

guard let self = self else { return ""}

var encodeString = ""
if let resString = resString {
encodeString = resString.kt.encodingBase64() ?? ""
}

return encodeString
}

// atob函数
let atob : @convention(block) (String?) -> String = { [weak self]
resString -> String in

guard let self = self else { return ""}

var decodeString = ""
if let resString = resString {
decodeString = resString.kt.decodeBase64() ?? ""
}

return decodeString
}

let global = JSValue(newObjectIn: self)!
global.setValue(setTimeout, forProperty: "setTimeout")
global.setValue(clearTimeout, forProperty: "clearTimeout")
global.setValue(setInterval, forProperty: "setInterval")
global.setValue(clearInterval, forProperty: "clearInterval")
global.setValue(btoa, forProperty: "btoa")
global.setValue(atob, forProperty: "atob")
global.setValue(onKTRequestJSCallEventBlock, forProperty: onKTRequestJSCallEvent)
global.setValue(onKTResponseJSCallEventBlock, forProperty: onKTResponseJSCallEvent)

globalObject.setValue(global, forProperty: "global")


// Native内接收JS请求事件的方法名称
let onRequestJSCallEventBlock : @convention(block) (String) -> Void = { [weak self]
(jsonString:String) -> Void in

// 调用处理
self?.eventQueue.async {
// 调用增强方法
if let jsCallNative = self?.jsCallNative {
jsCallNative(jsonString)
}
}
}

// Native内接收JS回调事件的方法名称
let onResponseJSCallEventBlock : @convention(block) (String) -> Void = { [weak self]
(jsonString:String) -> Void in

// 调用处理
self?.eventQueue.async {
// 调用回复方法
if let jsResponseNative = self?.jsResponseNative {
jsResponseNative(jsonString)
}
}
}
// Native内接收JS请求事件的方法名称
self.setObject(onRequestJSCallEventBlock, forKeyedSubscript: onKTRequestJSCallEvent as NSString)
// Native内接收JS回调事件的方法名称
self.setObject(onResponseJSCallEventBlock, forKeyedSubscript: onKTResponseJSCallEvent as NSString)
}

引用

JavaScript 中的 Event Loop 事件循环、微任务与宏任务

搞清事件循环、宏任务、微任务

JS 基础篇 - 宏任务与微任务 & EventLoop事件循环

最近的文章

HarmonyOS - 使用画布绘制自定义图形 (Canvas)

Canvas提供画布组件,用于自定义绘制图形,开发者使用CanvasRenderingContext2D对象和OffscreenCanvasRenderingContext2D对象在Canvas组件上进行绘制,绘制对象可以是基础形状、文本、图片等。 使用画布组件绘制自定义图形可以由以下三种形式在 …

, , , 开始阅读
更早的文章

HarmonyOS - Web 运行流程原理解析

Web渲染流程初始化 webview -&gt; 请求页面 -&gt; 下载数据 -&gt; 解析HTML -&gt; 请求 js&#x2F;css 资源 -&gt; dom 渲染 -&gt; 解析 JS 执行 -&gt; JS 请求数据 -&gt; 解析渲染 -&gt; 下载渲染图片 初始 …

, , , 开始阅读
comments powered by Disqus