JS异步编程,1/3
Posted 小韩说课
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JS异步编程,1/3相关的知识,希望对你有一定的参考价值。
今天整理一下JS中异步相关问题,缘起是同事的讨论。大概是三部分,1,异步是什么。2异步的语法。3异步的实现eventloop。 今天是第一部分,有以下的话题组成:
什么是异步?为什么需要异步执行?JS的语法单线程
什么是异步?
异步(async)是对应同步(sync)来说的,想理解异步就要先说同步。同步执行直观的理解就是代码顺序就是执行顺序,例如:
console.log("A")
console.log("B")
console.log("C")
代码的执行结果,是 A,B,C。这样,就是同步代码,执行顺序与编写顺序保持一致。
对比下面的异步代码:
console.log("A")
// setTimeout 就是异步代码
setTimeout(()=>{
console.log("B")
}, 0)
console.log("C")
代码的执行结果,是 A,C,B。编写顺序上,我们先输出的B,但是执行结果却是先输出的C。本例中,setTimeout
函数就是异步执行,也就是当执行的到 setTimeout
时,内部的代码不会立即执行,而是将其放在异步执行队列中等待执行。同时 setTimeout
后边的代码就开始执行,也就是说输出C,没有等到输出B执行完,就开始执行了。
本例中,setTimeout
函数就是异步执行。
为什么需要异步执行?
异步执行要解决的问题是有些非CPU密集型程序会导致CPU闲置的问题。
再看上面的例子,我将 setTimeout
的间隔调大到1000ms,再思考:
console.log("A")
// setTimeout 就是异步代码
setTimeout(()=>{
console.log("B")
}, 1000)
console.log("C")
若输出B不是异步执行的而是同步执行的,那就意味着必须要等待1000ms,B输出完毕后,C才会输出。但是问题是,C的输出与输出B没有任何关系,同时在这1000ms内,我们除了等待,什么都没做,这就是所说的CPU被闲置。可见若没有异步执行,CPU的性能浪费是很严重的。
最常见的非CPU密集型操作,就是IO操作,无论是磁盘IO还是网络IO尤其是网络IO。在等待网络传输(或者磁盘读取)的过程中,CPU一直处于闲置状态,这个时候,就必须要让CPU运作起来,才可以充分发挥计算机性能。所以网络操作一般都是异步操作。
异步执行还有一个问题,就是若某些操作需要依赖于异步操作的结果。那如何保证这些操作的执行时机呢? JS提供了多种语法方案供我们使用,例如:事件驱动,Promise,Generator,async,await等。
JS的语法单线程
一个语法的问题需要注意,就是JS的语法层面是单线程的,就是不能同时执行。这样就带来了一个问题,就是既然是单线程的,那异步的代码是如何执行的呢?
一定要注意,单线程指的是JS的语法层面是单线程的,而不是说浏览器或Node在执行JS代码时是单线程的。
看下面的例子,说明语法层面的单线程:
console.log("A AT ", (new Date()).toLocaleTimeString())
// setTimeout 异步,1000ms(1s)后执行输出B和时间
setTimeout(()=>{
console.log("B ", (new Date()).toLocaleTimeString())
}, 1000)
// 大循环,循环很多次,为了保证循环时间大于1000ms
for (let i = 0, num=999999999; i <= num; ++ i) {
if (0 == i) {
console.log("Loop first Run at ", (new Date()).toLocaleTimeString())
}
if (num == i) {
console.log("Loop last Run at ", (new Date()).toLocaleTimeString())
}
}
结果:
A AT 21:57:58
Loop first Run at 21:57:58
Loop last Run at 21:58:01
B 21:58:01
上面的例子中,我们做了一个定时器,异步执行在1000ms后,之后执行一个for循环,很多次循环,执行时间大于了1000ms。从执行结果上看,当达到了1000ms时,并没有立即执行输出B,而是要等到for循环执行完毕后,才会执行已经到达时间的输出B。
这个语法现象,就是JS的语法单线程,就是执行着for的任务,不会中间停止,而是需要for任务执行完毕后,才会考虑接下来的任务!
由于上面的执行机制,出现了下面的循环的写法,目的是拆解多次的for循环,注意是使用 setTimeout(func, 0)
来实现的:
console.log("A AT ", (new Date()).toLocaleTimeString())
// setTimeout 就是异步代码
setTimeout(()=>{
console.log("B ", (new Date()).toLocaleTimeString())
}, 1000)
let i = 0
function userFor() {
let num = 999
if (i > num) {
return
}
if (0 == i) {
console.log("Loop first Run at ", (new Date()).toLocaleTimeString())
}
if (num == i) {
console.log("Loop last Run at ", (new Date()).toLocaleTimeString())
}
++i
setTimeout(()=>{
userFor()
}, 0)
}
userFor()
结果:
A AT 22:11:10
Loop first Run at 22:11:10
B 22:11:11
Loop last Run at 22:11:12
从结果上看,1000ms的计时器在时间到达时,被理立即执行,而没有被下面的循环阻塞到。因为下面的循环也是利用settimeout异步实现的。但是改代码的循环性能却大大降低,除非有非常明确的原因,否则不建议使用!
待续…
接下来:事件驱动,promise,generator,async,await,eventloop。
以上是关于JS异步编程,1/3的主要内容,如果未能解决你的问题,请参考以下文章