JavaScript-异步

2020/7/4 JavaScript
// 进程和线程
1. 进程:资源分配的最小单位
2. 线程:CPU 调度的最小单位
3. 线程是依附于进程的,一个进程可有多个线程
----------------------------------------------------------------------------------------------
// 浏览器是多进程、多线程
1. 网络进程:负责网络资源加载
2. GPU 进程:负责浏览器界面的渲染,如 3D 绘制
3. 插件进程
4. 存储进程
5. 音频进程
6. 浏览器主进程:负责控制浏览器除标签页(渲染进程)外的界面、地址栏、状态栏、前进后退、刷新等
7. 渲染进程:负责界面渲染、脚本执行、事件处理等,默认情况下,每个 Tab 会创建一个渲染进程
  1. JS 引擎线程:负责解析和执行 JSJS 引擎线程和 GUI 渲染线程是互斥的,同时只能一个在执行
  2. GUI 渲染线程:解析 html 和 css,构建 DOM 树、CSSOM 树、Render 渲染树和绘制页面等
  3. 事件触发线程:主要用于控制事件循环,比如计时器(setTimeout/setInterval),异步网络请求等
      会把任务添加到事件触发线程中,当任务符合触发条件触发时,就把任务添加到待处理队列的队尾,等 JS 引擎线程处理
  4. 定时器触发线程:setTimeout 和 setInteval 计时的线程,定时的计时并不是由 JS 引擎线程负责的
      因为如果 JS 引擎线程阻塞的话会影响计时的准确性
  5. 异步 http 请求线程:ajax 的异步请求,fetch 请求等,ajax 同步请求不会产生异步任务
----------------------------------------------------------------------------------------------
// 异步操作
1. 异步操作一般是浏览器的两个或两个以上的线程共同完成
2. ajax 异步请求:异步 http 请求线程 + JS 引擎线程
3. setTimeout:定时触发线程 + JS 引擎线程 + 事件触发线程
----------------------------------------------------------------------------------------------
// 浏览器事件循环机制
1. 一次循环执行任务队列中的一个宏任务
2. 然后执行所有的微任务
----------------------------------------------------------------------------------------------
// 同源窗口之间共享事件循环
1. 如果一个窗口打开了另一个窗口,它们可能会共享一个事件循环
2. 如果窗口是包含在 iframe 中,则它可能会和包含它的窗口共享一个事件循环
3. 在多进程浏览器中多个窗口碰巧共享了同一个进程,因为渲染进程有数量限制,当达到一定数量时就会复用
----------------------------------------------------------------------------------------------
// Node 事件循环,主要有以下几个阶段
1. timers:此阶段执行由 setTimeout 和 setInterval 调度的回调
2. pending callbacks:执行延迟到下一个循环迭代的 io 回调
  1. 为什么是下一个?
    因为每个队列单次有最大数量的限制,不能保证全部执行完,只能下次
3. dle/prpare:仅仅内部使用
4. poll:检索新的 io 事件,执行与 io 相关的回调(除了 close 回调、由计时器调度的回调和 setImmediate)
    适当时,节点将在此处阻塞
5. check:setImmediate 回调在这里被调用
6. close callbacks:一些关闭的回调,如 socket.on('close')
----------------------------------------------------------------------------------------------
// 异步
1. 同步会阻塞代码,异步不会,基于js是单线程语言,只能同时做一件事
2. js和DOM渲染公用一个线程,因为js可以修改DOM结构
3. 异步应用场景: 网络请求(ajax、图片加载)、定时任务(setTimeout)
4. async/await是消灭异步回调的终极武器
5. js还是单线程,还得是有异步,还得是基于event loop
6. async/await只是一个语法糖,本质是对 generator 的一种封装 // 语法不一样,实现效果一样
7. await的下面的代码都可以看做是callback里的内容,即异步,相当于 Promise.then
8. for in以及forEach和for是常规的同步变量,一次性全部一起执行,不会等待任何东西,一瞬间遍历完,一瞬间执行几遍
9. for of常用于异步的变量,有顺序,执行完一个再执行下一个
----------------------------------------------------------------------------------------------
// queueMicrotask
1. 在事件循环结束前插入一个微任务,比 setTimeout(fn, 0) 更快
2. 注意:添加的微任务,没有提供取消的手段
3. 语法:queueMicrotask(fn)
----------------------------------------------------------------------------------------------
// promise
1. 通过promise这个构造函数来创建一个对象
2. 这个promise对象,有三种状态pending(等待)、resolved(成功)、rejected(失败)
3. promise构造函数有一个参数,这个参数是同一个回调,这个回调接收两个参数,都能改变
4. promise对象的状态,第一个参数可以将状态从pending变成resolved,第二个将pending变成rejected
5. then有两个参数第一个代表成功,第二个代表失败
6. 在使用promise时,在then最后记得return promise实例或promise对象,以便可以影响的后面的then操作
7. 初始化promise时,传入的函数会立刻被执行
8. then正常返回resolved,里面有报错则返回rejected
9. catch正常返回resolved,里面有报错则返回rejected
10. rejected触发catch回调
11. resolved触发then回调
12. const promise = new Promise((resolve, reject) => {
      setTimeout(function() {
        let num = Math.floor(Math.random() * 100)
        if (num % 2 === 0) {
          resolve(num) // 成功
        } else {
          reject(num) // 失败
        }
      }, 1000)
   })
   promise.then(num => {
     return `resolve${num}` // 成功
   })
   promise.catch(num => {
     return `reject${num}` // 失败
   })
   Promise.all([p1, p2]).then(resolve => {}, reject => {}) // 整合,全部都有才能继续,如果有一个失败则进入 catch
   Promise.allSettled([p1, p2]).then(resolve => {}, reject => {}) // 整合,不论成功失败,结果全部返回
   Promise.race([p1, p2]).then(resolve => {}, reject => {}) // 竞争,不论成功失败,谁快用谁
   Promise.any([p1, p2]).then(resolve => {}, reject => {}) // 竞争,谁最快成功就用谁
13. const request = url => {
       return new Promise((resolve ,reject) => {
           $.get(url, params => {
               resolve(params)
           })
        })
      }
    request(url).then(params1 => {
       return request(params1.url)
    }).then(params2 => {
       return request(params2.url)
    }).then(params3 => {
       console.log(params3)
    }).catch(err => throw new Error(err))
14.new Promise 回调中 resolve 或 reject 后面的代码会继续执行
    但是如果在抛出异常,它的状态是不会改变的,如在 resolve 后抛出异常,它的状态还是成功
15. 在 Promise 的 catch 中可以捕获 throw new Error() 的错误和 reject 的错误
----------------------------------------------------------------------------------------------
// js 执行顺序
1. 从前往后,一行一行执行
2. 如果有执行报错,则停止下面的代码运行
3. 先把同步执行完再执行异步
----------------------------------------------------------------------------------------------
// event loop
1. call stack // 调用栈
2. webApis // 宏任务挂起的地方
3. micro task queue // 微任务挂起的地方
4. callback queue // event loop每次查询的地方
5. event loop // 永动机查询
----------------------------------------------------------------------------------------------
// event loop (事件循环/事件轮询)
1. 同步代码,一行一行放在call stack调用栈执行
2. 遇到异步,会先记录下来,等待时机(定时、网络请求等)
3. 时机到了,就移动到callback queue
4. 如果call stack为空(即同步代码执行完),Event Loop开始工作
5. 轮询查找callback queue,如有则移动到call stack执行
6. 然后继续轮询查找(永动机一样)
----------------------------------------------------------------------------------------------
// 宏任务和微任务
1. 宏任务: setTimeout、setInterval、ajax、DOM事件
2. 微任务: promise、async/await、process.nextTick
3. 微任务比宏任务执行时机要早, 因为一个是ES规定的, 一个是W3C规定的
----------------------------------------------------------------------------------------------
// 宏任务和微任务的区别
1. 宏任务会在DOM渲染后触发,微任务在DOM渲染前触发
2. 微任务是ES6(ECMA)语法规定的,宏任务是浏览器规定的(W3C)
3. 宏任务会等待时机放在webApis中再去放到callback queue中
4. 微任务会等待时机放到micro task queue中
----------------------------------------------------------------------------------------------
// 有宏任务和微任务的event loop
1. 当call stack每次轮询清空后,或每次轮询结束后
2. 会执行当前的微任务(全部)
3. 再尝试DOM渲染
4. 再触发event loop
5. 再执行当前的宏任务(一个)
----------------------------------------------------------------------------------------------
// DOM事件和event loop关系
1. js是单线程的
2. 异步(setTimeout、ajax等),使用回调,基于event loop
3. DOM事件也使用回调,基于event loop // click事件
4. xx.clci、dispathEvent 会导致事件同步调度
----------------------------------------------------------------------------------------------
// DOM渲染和event loop关系
1. call stack空闲时(call stack清空,同步代码执行完,每次轮询结束)会先执行当前的微任务(全部)
2. 尝试DOM渲染,如果DOM结构有改变则重新渲染
3. 再去触发event loop
4. 再执行当前的宏任务
----------------------------------------------------------------------------------------------
// 其他
1. Js引擎为了让microtask尽快的输出,做了一些优化,连续的多个then(3)如果没有reject或者resolve,
    会交替执行then而不至于让一个堵太久完成用户无响应,不单单v8这样其他引擎也是这样,
    因为其实promuse内部状态已经结束了。这块在v8源码里有完整的体现
2. settimeout 最低延迟是 1ms,即使传入的是 0ms,如果延迟时间大于 231 次方 -1,那么也会变成 1ms
----------------------------------------------------------------------------------------------
// 手写 promise A+ 规范
class MyPromise {
    state = 'pending' // 状态,'pending' 'fulfilled' 'rejected'
    value = undefined // 成功后的值
    reason = undefined // 失败后的原因

    resolveCallbacks = [] // pending 状态下,存储成功的回调
    rejectCallbacks = [] // pending 状态下,存储失败的回调

    constructor(fn) {
        const resolveHandler = (value) => {
            if (this.state === 'pending') {
                this.state = 'fulfilled'
                this.value = value
                this.resolveCallbacks.forEach(fn => fn(this.value))
            }
        }

        const rejectHandler = (reason) => {
            if (this.state === 'pending') {
                this.state = 'rejected'
                this.reason = reason
                this.rejectCallbacks.forEach(fn => fn(this.reason))
            }
        }

        try {
            fn(resolveHandler, rejectHandler)
        } catch (err) {
            rejectHandler(err)
        }
    }

    then(fn1, fn2) {
        fn1 = typeof fn1 === 'function' ? fn1 : (v) => v
        fn2 = typeof fn2 === 'function' ? fn2 : (e) => e

        if (this.state === 'pending') {
            const p1 = new MyPromise((resolve, reject) => {
                this.resolveCallbacks.push(() => {
                    try {
                        const newValue = fn1(this.value)
                        resolve(newValue)
                    } catch (err) {
                        reject(err)
                    }
                })

                this.rejectCallbacks.push(() => {
                    try {
                        const newReason = fn2(this.reason)
                        reject(newReason)
                    } catch (err) {
                        reject(err)
                    }
                })
            })
            return p1
        }

        if (this.state === 'fulfilled') {
            const p1 = new MyPromise((resolve, reject) => {
                try {
                    const newValue = fn1(this.value)
                    resolve(newValue)
                } catch (err) {
                    reject(err)
                }
            })
            return p1
        }

        if (this.state === 'rejected') {
            const p1 = new MyPromise((resolve, reject) => {
                try {
                    const newReason = fn2(this.reason)
                    reject(newReason)
                } catch (err) {
                    reject(err)
                }
            })
            return p1
        }
    }

    // 就是 then 的一个语法糖,简单模式
    catch(fn) {
        return this.then(null, fn)
    }
}

MyPromise.resolve = function (value) {
    return new MyPromise((resolve, reject) => resolve(value))
}
MyPromise.reject = function (reason) {
    return new MyPromise((resolve, reject) => reject(reason))
}

MyPromise.all = function (promiseList = []) {
    const p1 = new MyPromise((resolve, reject) => {
        const result = [] // 存储 promiseList 所有的结果
        const length = promiseList.length
        let resolvedCount = 0

        promiseList.forEach(p => {
            p.then(data => {
                result.push(data)

                // resolvedCount 必须在 then 里面做 ++
                // 不能用 index
                resolvedCount++
                if (resolvedCount === length) {
                    // 已经遍历到了最后一个 promise
                    resolve(result)
                }
            }).catch(err => {
                reject(err)
            })
        })
    })
    return p1
}

MyPromise.race = function (promiseList = []) {
    let resolved = false // 标记
    const p1 = new Promise((resolve, reject) => {
        promiseList.forEach(p => {
            p.then(data => {
                if (!resolved) {
                    resolve(data)
                    resolved = true
                }
            }).catch((err) => {
                reject(err)
            })
        })
    })
    return p1
}
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
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
Last Updated: 2024/7/31 12:57:25
    飘向北方
    那吾克热-NW,尤长靖