javascript等待异步线程完成_JavaScript采用单线程模式工作的原因,如何解决单线程模式出现的问题?...

1c91346057e5c39e0e4f1159321fe6ba.png
JavaScript采用单线程模式工作的原因,如何解决单线程模式出现的问题? · 语雀​www.yuque.com
文章首发于语雀。

页面交互的核心是 Dom 操作,多线程操作 Dom 肯定会发生问题,所以就设计为单线程。
这样,JS 执行环境中负责执行代码的线程就只有一个了。
那么如果有多个任务要执行,怎么办呢?那就排队!
这种模式的优点就是更安全、更简单。
但同时就会带来缺点:耗时任务会拖延排在它后面所有任务的执行
这就是单线程 JavaScript 的严重问题:无法同时处理大量的耗时任务。同时也就出现了耗时任务阻塞程序执行的问题。那么为了解决这个问题,JavaScript 把任务的执行模式分成了两种:同步模式 和 异步模式。
那么下面我们主要讲解关于异步编程的知识。

  1. 同步模式和异步模式的差异和意义?
  2. JavaScript 单线程如何实现异步模式?(事件循环和消息队列)
  3. JavaScript 异步编程有几种方法?
  4. Promise 异步编程解决方案如何上手?其中牵扯到的宏任务和微任务是什么?
  5. Generator 异步编程解决方案如何上手?
  6. 如何使用 Async/Await 语法糖写出更扁平的异步代码?

同步模式和异步模式的差异和意义?

同步执行模式

代码中的任务依次执行,执行顺序和代码顺序一致。
示例如下:

console

执行简述:

  1. JavaScript 执行引擎首先会在调用栈里压入一个匿名函数,同时把所有代码放入匿名函数中并执行,然后逐行执行每一行的代码。
  2. 第 1 行压入调用栈执行,执行完毕后弹出。
  3. bar 函数和 foo 函数的声明不会触发调用,会继续向下走。
  4. 第 9 行 foo 函数压入调用栈调用,不弹出。
  5. 第 6 行压入调用栈执行,执行完毕后弹出。
  6. 第 7 行 bar 函数压入调用栈调用,不弹出。
  7. 第 3 行压入调用栈执行,执行完毕后弹出。
  8. bar 函数执行完毕,从调用栈弹出。
  9. foo 函数执行完毕,从调用栈弹出。
  10. 第 10 行压入调用栈执行,执行完毕后弹出。
  11. 整体代码执行结束,调用栈清空。

总结:
同步模式就是代码一行一行的顺序执行,如果某行耗时严重,程序将被阻塞,阻塞对于用户来讲就是卡死。
因此我们必须要使用异步模式来解决程序中无法避免的耗时操作,避免页面程序卡死。

异步执行模式:

Api 不会等待任务结束才会执行下个任务,耗时任务开始后就立即执行下个任务,异步任务的后续一般通过回调函数的方式定义,异步任务执行完毕时会自动执行回调函数。
异步模式的缺点:代码执行顺序比较跳跃,理解起来比较混乱。
示例如下:

console
需要注意的是,异步模式比同步模式多出了三个概念:事件循环、消息队列、平台API(Web 就是 Web Apis)。 也有人把消息队列称为回调队列。

执行简述:

  1. JavaScript 执行引擎首先会在调用栈里压入一个匿名函数,同时把所有代码放入匿名函数中并执行,然后逐行执行每一行的代码。
  2. 第 1 行压入调用栈执行,执行完毕后弹出。
  3. 第 3 行 setTimeout 压入调用栈,这里因为函数是异步调用,Web Apis 会在内部为 timer 函数开启一个 1.8s 的倒计时器然后放到一边儿玩时间沙漏去了,而这个沙漏是单独工作不受 JavaScript 单线程影响的,会立即开始倒数。同时需要注意的是,对于 setTimeout 来说,它已经调用完毕了,于是 setTimeout 会弹出调用栈。
  4. 第 7 行 setTimeout 压入调用栈,timer_2 开启 1s 倒计时器也玩沙漏去了,然后 setTimeout 弹出调用栈。
  5. 第 14 行压入调用栈执行,执行完毕后弹出。
  6. 此时所有代码执行完毕,清空调用栈。
  7. Event Loop 会始终监听调用栈和消息队列,一旦调用栈里所有的任务都结束了,那么事件循环就会从消息队列当中取出第一个回调函数。但此时消息队列是空的不存在未执行的函数,因为两个 timer 还都在玩沙漏。
  8. 在时间过去 1s 后,timer_2 率先结束玩耍时间,进入了消息队列的第一个位置。
  9. 在时间又过去 0.8s 后,timer_1 紧随其后也进入了消息队列,此时它会被放置在第二个位置。
  10. 一旦消息队列当中发生了变化,事件循环就会监听到。然后它会把消息队列当中的第一个也就是 timer_2 函数压入调用栈。
  11. 第 8 行压入调用栈执行,执行完毕后弹出。(此时调用栈里 timer_2 还在)
  12. 第 9 行 setTimeout 压入调用栈,inner 玩沙漏去后,setTimeout 弹出。
  13. timer_2 弹出,清空调用栈。
  14. 事件循环监听到调用栈无任务执行,把消息队列中第一位 timer_1 取出压入调用栈。
  15. 第 4 行压入调用栈执行,执行完毕后弹出。
  16. 时间过去 1s 后,inner 进入消息队列。
  17. 事件循环从消息队列取出 inner 压入调用栈。
  18. 第 10 行压入调用栈执行,执行完毕后弹出。
  19. inner 弹出,清空调用栈。 需要注意的是,示例中的任务(setTimout)耗时是在玩沙漏,这里的耗时可以是任意耗时任务,都会独立在 JavaScript 单线程外执行,执行完毕后就会把回调函数放入消息队列,这中间的所有环节都不会阻塞单线程那边的代码执行,也不会去管调用栈和消息队列里是什么情况,异步任务执行结束了就会把回调排在消息队列后面。

总结:
我们从执行过程中可以看出,异步模式核心是通过事件循环和消息队列实现的。
而我们解决耗时任务阻塞程序的取巧之处,就是 JavaScript 确实是单线程的,而浏览器却是多线程的,也就是我们利用了 JavaScript 提供的一些 异步 Api,如:setTimeout 等。这些 异步 Api 内部就会有独立的线程做需要等待的操作(耗时任务)。而我们一直说的单线程则指的是:执行代码的线程是一个线程。
还需要注意的是,同步模式和异步模式指的并不是我们代码的抒写方式,而是我们代码里使用的 Api 是以同步模式还是异步模式的方式工作的。

致谢:感谢拉勾教育提供的学习环境。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值