JS的异步机制

JavaScript作为单线程语言,通过调用栈、事件循环和任务队列来实现异步操作。同步任务按顺序执行,而异步任务如定时器、网络请求等由异步线程处理,完成后回调函数进入任务队列。事件循环检查调用栈,执行完同步任务后处理任务队列中的宏任务和微任务,微任务优先级高于宏任务。Promise和process.nextTick是微任务的示例,setTimeout等是宏任务的示例。
摘要由CSDN通过智能技术生成

JavaScript如何实现异步

既然js是单线程语言,单线程意味着JavaScript 同时只能执行一个任务,其他任务都必须在后面排队等待,也就是说一段js代码中,必须从第一行按顺序执行到最后一行。

那么如何在单线程中实现异步呢?

调用栈 Call Stack

了解异步之前,需要先知道正常的同步机制是什么样子的。

调用栈是一种栈结构
调用栈是JS引擎执行程序的一种机制。程序每调用一层函数(方法),引擎就会生成它的栈帧,栈帧里面保存了函数的执行上下文,然后将它压入调用栈。栈是一个后进先出的结构,直到最里层的函数执行完,引擎才开始将最后加入的栈帧从栈中弹出。

简单来说,调用栈就是用来保存程序执行的上下文和执行代码的。为什么需要这个东西,而不是直接去执行呢,个人理解是因为代码中会有嵌套结构调用来调用去的,使用栈结构就可以保存这些调用记录,一层层的调用和返回。
以一个同步任务为例:

const foo = () => {
  console.log(1)
  console.log(2)
}
foo()
console.log(3)

在这个例子中,控制台打印123。调用栈的入栈出栈顺序如下:

foo -> 入栈
console.log(1) -> 入栈
console.log(1) <- 出栈
console.log(2) -> 入栈
console.log(2) <- 出栈
foo <- 出栈
console.log(3) -> 入栈
console.log(3) <- 出栈

在这个同步任务执行过程中,我们可以看到该结构是按顺序一行一行压栈执行的,下面的代码要等前面的代码全部执行完毕并弹栈了才能执行。在这套机制的基础上如果想实现异步,就要需要解决两个问题:
1.区分出同步任务和异步任务。
2.发现异步任务时,进行另一套异步处理机制,继续执行下面的代码,避免阻塞。

事件循环与任务队列

JS设计了一个任务队列和事件循环来实现异步过程。

异步思路简单来说,就是我们通过判断api的类型进行分类处理。如果是同步的api,我们就按照上述的流程同步执行。如果发现异步api(包括定时器、网络请求等等),我们直接交给异步线程来处理。主线程不受干扰,继续执行代码避免阻塞。那么异步线程处理完了的回调又该在何时执行呢?这里我们使用一个队列,当异步线程处理完的回调存储在队列中,当主线程中的调用栈执行完了,也就是那些同步任务执行完了,再过来执行队列里的异步回调。流程如下:

1.主线程执行
2.代码在压入调入栈之前会进行判断,如果是同步API则正常入栈执行。如果是异步API(如定时器)则交给异步的线程去处理(如定时器线程在后台执行),然后调入栈继续压栈下一行代码。
3.异步线程执行完成后,事件触发线程将异步的回调放入任务队列中。(事件触发线程是JS引擎中的一个线程)
4.主线程的调用栈执行完了,此时主线程会去任务队列中看看有没有任务。
5.主线程如果发现任务队列中有任务,就按上述继续压栈执行。
6.不断重复上述过程,直至代码执行完成。

  • 存储异步执行任务的回调的一个队列称为任务队列。

  • 每次调用栈被清空后,都会在消息队列中读取新的任务到调用栈中,如果没有新的任务,就会等待,直到到有新的任务。这样循环往复的过程就叫事件循环。

setTimeout(function(){
  console.log(0);
},0)

console.log(1);

按照这个异步思路,上述代码的输出为10,而非01也就可以理解。只要你是个异步的api,那么一定是在同步api后面执行的,和异步处理所花费的时间无关。

宏任务和微任务

宏任务和微任务是任务队列中的两种任务。为什么要把任务队列中的任务进行分类呢?

这种设计是为了给异步任务分一个高低贵贱,给紧急任务一个插队的机会,否则新入队的任务永远被放在队尾。而微任务的优先度永远比宏任务更高。调用栈从任务队列压栈时,会优先入栈微任务。

宏任务(macrotask):

  • 异步 Ajax 请求
  • setTimeout
  • setInterval
  • 文件操作
  • 其它宏任务

微任务(microtask):

  • Promise.then
  • catch 和 finally
  • process.nextTick
  • 其它微任务

个人理解

即使js代码通过一系列方法实现了异步操作,但有些机制仍然是要靠多线程来支持的。我个人的理解,就算有任务队列和事件循环,也不可能靠一个单线程的方式实现异步操作,你总归还是需要另外一个线程来执行或者回调这个异步操作,也就是定时器线程、事件触发线程等等。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值