async、await和事件循环
总览
回调地狱、链式调用、async与await、事件循环笔记总结
# JS单线程
单线程指的是,JavaScript
只在一个线程上运行。也就是说,JavaScript
同时只能执行一个任务,其他任务都必须在后面排队等待。
JavaScript
之所以采用单线程,而不是多线程,跟历史有关系。JavaScript
从诞生起就是单线程,原因是不想让浏览器变得太复杂,因为多线程需要共享资源、且有可能修改彼此的运行结果,对于一种网页脚本语言来说,这就太复杂了。
# async
async
和 await
关键字让我们可以用一种更简洁的方式写出基于 Promise
的异步行为,而无需刻意地链式调用 promise
。
async 函数返回的是一个 Promise 对象
# await
通常使用await是后面会跟上一个表达式,这个表达式会返回一个Promise;
那么await会等到Promise的状态变成fulfilled状态,之后继续执行异步函数;
# 回调地狱
回调函数的层层嵌套
var sayhello = function (name, callback) {
setTimeout(function () {
console.log(name);
callback();
}, 1000);
}
sayhello("first", function () {
sayhello("second", function () {
sayhello("third", function () {
console.log("end");
});
});
});
//first second third end
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 链式调用方案
优化回调地狱代码
第一个 then 方法指定的回调函数,返回的是另一个Promise
对象。这时,第二个then
方法指定的回调函数,就会等待这个新的Promise
对象状态发生变化。如果变为resolved
,就继续执行第二个 then 里的回调函数
var sayhello = function (name) {
return new Promise(function (resolve, reject) {
setTimeout(function () {
console.log(name);
resolve(); //在异步操作执行完后执行 resolve() 函数
}, 1000);
});
}
sayhello("first").then(function () {
return sayhello("second"); //仍然返回一个 Promise 对象
}).then(function () {
return sayhello("third");
}).then(function () {
console.log('end');
}).catch(function (err) {
console.log(err);
})
//输出:first second third end
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 生成器调用方案
继续优化回调地狱
var sayhello = function (name) {
return new Promise(function (resolve, reject) {
setTimeout(function () {
console.log(name);
resolve('resolve'); //在异步操作执行完后执行 resolve() 函数
}, 1000);
});
}
function* getData() {
console.log('start');
const res1 = yield sayhello('rio1')
console.log('res1', res1);
const res2 = yield sayhello('rio2')
console.log('res2', res2);
const res3 = yield sayhello('rio3')
console.log('res3', res3);
console.log('end');
}
const gen = getData()
gen.next('g1').value.then(res1=>{
console.log(res1);
gen.next('g2').value.then(res2=>{
gen.next('g3').value.then(res3=>{
gen.next()
})
})
})
// start
// rio1
// resolve
// res1 g2
// rio2
// res2 g3
// rio3
// res3 undefined
// end
-------------------------------------
// 生成器封装优化
function execGetData(genfn) {
const gen = genfn()
function exec(res) {
const result = gen.next(res)
if (result.done) return
result.value.then(res=>{
exec(res)
})
}
exec()
}
execGetData(getData)
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
# async与await方案
回调地狱最终解决方案
var sayhello = function (name) {
return new Promise(function (resolve, reject) {
setTimeout(function () {
console.log(name);
resolve('resolve');
}, 1000);
});
}
async function getData() {
console.log('start');
const res1 = await sayhello('rio1')
console.log('res1', res1);
const res2 = await sayhello('rio2')
console.log('res2', res2);
const res3 = await sayhello('rio3')
console.log('res3', res3);
console.log('end');
}
const gen = getData()
// start
//rio1
// res1 resolve
// rio2
// res2 resolve
// rio3
// res3 resolve
// end
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
# 事件循环
相关知识回顾 进程与线程 浏览器中的线程与进程
为什么要有事件循环?
- 只要有一个任务耗时很长,后面的任务都必须排队等着,会拖延整个程序的执行
如果代码要访问一些高延迟的资源,比如向远程服务器发送请求并等待响应,那么就会出现长时间的等待,这时 CPU
完全可以不管 IO
操作,挂起处于等待中的任务,先运行排在后面的任务。等到请求操作返回了结果,再回过头,把挂起的任务继续执行下去。这种机制就是 JavaScript
内部采用的 “事件循环”机制(Event Loop
)。
为了利用多核 CPU
的计算能力,HTML5
提出 Web Worker
标准,允许 JavaScript
脚本创建多个线程,但是子线程完全受主线程控制,且不得操作 DOM
。所以,这个新标准并没有改变 JavaScript
单线程的本质。
异步操作都是放到事件循环队列里面,等待主执行栈来执行的,并没有专门的异步执行线程,同步和异步任务分别进入不同的执行环境,同步的进入主线程,即主执行栈,异步的进入任务队列。主线程内的任务执行完毕为空,会去任务队列读取对应的任务,推入主线程执行。 上述过程的不断重复就是我们说的 Event Loop (事件循环)。在事件循环中,每进行一次循环操作称为tick
宏任务: setTimeout , setlnterval, Ajax,DOM监听,宏任务一般由浏览器发起,在DOM渲染后触发 微任务: Promise的then回调等,微任务在DOM渲染前触发
在执行任何一个宏任务之前,都会先查看微任务队列中是否有任务需要执行也就是宏任务执行之前,必须保证微任务队列是空的; 如果不为空,那么就优先执行微任务队列中的任务(回调)
# 练习题
async function fn1 (){
console.log(1)
await fn2()
console.log(2) // 阻塞
}
async function fn2 (){
console.log('fn2')
}
fn1()
console.log(3)
// 1 fn2 3 2
-----------------------------
(async function () {
console.log('start');
const q = await 100
console.log('q', q);
const w = await Promise.resolve(200)
console.log('w', w);
const e = await Promise.reject(300)
console.log('e', e);
console.log('end');
})()
// start
// q 100
// w 200
// Uncaught (in promise) 300
---------------------------------------------------
const first = () => (new Promise((resolve, reject) => {
console.log(3)
let p = new Promise((resolve, reject) => {
console.log(7)
setTimeout(() => {
console.log(5)
resolve(6) // 不打印6的原因是 此时promise状态已经改变
}, 0)
resolve(1)
})
resolve(2)
p.then((arg) => {
console.log(arg,'p')
})
}))
first().then((arg)=>{
console.log(arg,'first');
})
console.log(4);
// 3 7 4 1'p' 2'first' 5
------------------------------------
console.log('a');
function req(url) {
console.log('req');
return new Promise((resolve)=>{
setTimeout(() => {
console.log('settimeout');
resolve(url)
}, 2000);
})
}
async function get() {
console.log('get');
const res = await req('await') // 阻塞 等到promise fulfilled为止
console.log("await res",res);
console.log('end');
}
get()
console.log('b');
// a get req b settimeout 'await res await' end
------------------------------
console.log('a');
function req(url) {
console.log('req');
return new Promise((resolve,reject)=>{
console.log('settimeout');
reject(url)
})
}
async function get() {
console.log('get');
try {
const res = await req('await')
} catch (error) {
console.log(error);
}
console.log('end');
}
get()
console.log('b');
// a get req settimeout b await end
-------------------------------
console.log('1');
setTimeout(function () {
console.log('2');
}, 10);
new Promise(resolve => {
console.log('3');
resolve();
setTimeout(() => console.log('4'), 10);
})
.then(function () {
console.log('5')
})
console.log('6');
// 1 3 6 5 2 4
-------------------------------
setTimeout(() => {
console.log("0")
}, 0)
new Promise((resolve, reject) => {
console.log("1")
resolve()
})
.then(() => {
console.log("2")
new Promise((resolve, reject) => {
console.log("3")
resolve()
})
.then(() => {
console.log("4")
})
.then(() => {
console.log("5")
})
})
.then(() => {
console.log("6")
})
new Promise((resolve, reject) => {
console.log("7")
resolve()
}).then(() => {
console.log("8")
})
// 1 7 2 3 8 4 6 5 0
----------------------------------------
async function a1() {
console.log('a1 sstart');
await a2()
console.log('a1 end');
}
async function a2() {
console.log('a2');
}
console.log("script starts");
setTimeout(() => {
console.log('setTimeout');
}, 0);
a1()
new Promise((res) => {
console.log('p1');
res()
}).then(() => {
console.log('p2');
})
console.log('script end');
// script starts
// a1 sstart
// a2
// p1
// script end
// a1 end
// p2
// setTimeout
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