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的主要内容,如果未能解决你的问题,请参考以下文章

VSCode自定义代码片段9——JS中的面向对象编程

ES7-Es8 js代码片段

理解js中的异步编程

JS高阶三(JS中的异步编程)

js 异步编程

理解js异步编程