JavaScript异步编程是什么? 异步编程都有哪些解决方案?

前言

身为前端,一直都知道异步编程怎么用,但是都没有记录过,此系列详细介绍一下我所理解的javascript中的异步编程。
链接扩展:
异步编程之–深度理解Promise
异步编程之–理解es6的Generators(生成器 )
异步编程之–理解es6中的Iterator(迭代器)

正文

异步编程是什么?为什么要使用异步编程?

我们都知道,javascript从诞生之日起就是一门单线程的非阻塞的脚本语言。这是由其最初的用途来决定的:与浏览器交互。

javascripty有两大特点:单线程+非阻塞

  • 单线程意味着,javascript代码在执行的任何时候,都只有一个主线程来处理所有的任务。单线程是必要的,也是javascript这门语言的基石,原因在其最初也是最主要的执行环境——浏览器中,我们需要进行各种各样的dom操作。

  • 试想一下 如果javascript是多线程的,那么当两个线程同时对dom进行一项操作,例如一个向其添加事件,而另一个删除了这个dom,此时该如何处理呢?因此,为了保证不会 发生类似于这个例子中的情景,javascript选择只用一个主线程来执行代码,这样就保证了程序执行的一致性。

  • 现如今人们也意识到,单线程在保证了执行顺序的同时也限制了javascript的效率,因此开发出了web worker技术。这项技术号称让javascript成为一门多线程语言。

  • 然而,使用web worker技术开的多线程有着诸多限制,例如:所有新线程都受主线程的完全控制,不能独立执行。这意味着这些“线程” 实际上应属于主线程的子线程。另外,这些子线程并没有执行I/O操作的权限,只能为主线程分担一些诸如计算等任务。所以严格来讲这些线程并没有完整的功能,也因此这项技术并非改变了javascript语言的单线程本质。

  • 单线程就好像100个人在1个售票窗口买票,而多线程就好像100个人在100个窗口买票。但是js的特点决定了不能多线程,所以衍生了单线程+非阻塞。

  • 而非阻塞则是当代码需要进行一项异步任务(无法立刻返回结果,需要花一定时间才能返回的任务,如I/O事件,ajax)的时候,主线程会挂起(pending)这个任务,然后在异步任务返回结果的时候再根据一定规则去执行相应的回调。

  • 这种就好像:100个人去买票,车站开放了1个窗口,但是车票的预订可以在网上或其他地方完成,到车站时候只需要排队取票。我们可以看到,开1个窗口,就相当于只有1个线程。然后把耗时的一些操作分成两部分,先把快速能做完的事情做了,这样保证它不会阻塞其他代码的运行。剩下耗时的部分再单独执行。这就是单线程阻塞式的异步实现机制。

javascript的另一个特点是“非阻塞”,那么javascript引擎到底是如何实现非阻塞异步呢?

具体JS如何实现异步?

方法:JS的事件循环机制(Event Loop)

在这里插入图片描述
单线程就意味着,所有任务需要排队。所有任务可以分成两种,一种是同步任务(synchronous),另一种是异步任务(asynchronous)。

同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;

异步任务指的是,不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。

异步执行的运行机制如下:

  • (1)所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。
  • (2)主线程之外,还存在一个"任务队列"(task queue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。
  • (3)一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
  • (4)主线程不断重复上面的第三步。

主线程从"任务队列"中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为Event Loop(事件循环)。

理解任务队列(task queue)

任务队列是放置异步任务事件的队列,任务队列可以存在多个,在同一任务队列内,按队列顺序被主线程取走;不同任务队列之间,存在着优先级,优先级高的优先获取(如用户I/O)。

任务队列的类型:

  • 宏任务队列 (macrotask queue)
    比如: script(整体代码), setTimeout, setInterval, setImmediate, I/O, UI rendering。
  • 微任务队列 (microtask queue)
    例如: process.nextTick, Promises, Object.observe, MutationObserver

为了更好理解,下面来分析一段代码:

console.log('script start');
setTimeout(() => {
  console.log('setTimeout 1');
}500);
setTimeout(() => {
  console.log('setTimeout 2');
});

Promise.resolve()
.then(() => {
  console.log('promise 1');
})
.then(() => {
  console.log('promise 2');
});

console.log('script end');

上述代码的执行结果为:

> "script start"
> "script end"
> "promise 1"
> "promise 2"
> "setTimeout 2"
> "setTimeout 1"

我们来分析一下执行逻辑:

  • 1.浏览器从上往下执行,执行到console.log('script start');输出结果;
  • 2.执行到两个setTimeout时把的回调函数按顺序放入Macro Task的队列中。
  • 3.执行 Promise时 把两个 then 的回调函数放入 Micro Task 栈中。
  • 4.执行console.log('script end');输出结果。
  • 5.这里我们就可以看出来,macrotaskmicrotask 执行的特征:在宏任务和微任务同时存在的时候,首先按顺序执行Micro Task 栈中的所有任务,等Micro Task 栈中的所有任务执行结束后再按先进入栈先执行的规则来执行Macro Task的队列中任务。

异步编程都有哪些解决方案?

  • 回调函数(callback)–例:ajax
    详解:理解回调函数
    优点:简单、容易理解和实现。
    缺点:是不利于代码的阅读和维护,各个部分之间高度耦合,使得程序结构混乱、流程难以追踪(尤其是多个回调函数嵌套造成回调地狱的情况),而且每个任务只能指定一个回调函数。此外它不能使用 try catch 捕获错误,不能直接 return。

  • 事件监听-利用定时器(setTimeout)
    监听一个事件的执行,等到状态改变时在内部利用定时器执行其他操作。
    优点:便于理解,可以绑定多个事件,有利于实现模块化。
    缺点:是整个程序都要变成事件驱动型,运行流程会变得很不清晰。阅读代码的时候,很难看出主流程。

  • promise
    详解:Promise的特点和方法详解

    优点: 相比传统回调函数和事件更加合理和优雅,Promise是链式编程,有效的解决了令人头痛的回调地狱问题。
    缺点:不易理解,编程代码量并没有减少很多,而且Promise的结果有成功和失败两种状态,只有异步操作的结果,可以决定当前是哪一种状态,外界的任何操作都无法改变这个状态,在执行过成功,不能终止。

  • 生成器Generators
    详解:理解es6的Generators(生成器 )

    优点:相较于以上三种,既可以解决回调地狱又可以在函数中间暂停执行,使用同步的方式去编写异步代码,代码的可读性更好,很好的解决了JavaScript中异步的问题。
    缺点:手动执行时复杂,需要依赖执行器函数。在解决异步问题时对封装,promise等知识都需要用到,上手并不是很容易。

  • async awiat
    详解:async表示该函数要做异步处理。await表示后面的代码是一个异步操作,等待该异步操作完成后再执行后面的动作。如果异步操作有返回的数据,则在左边用一个变量来接收它。

    优点:很优秀,确实是一个既好用、又简单的异步处理方法!
    缺点:身为es7的语法,可能考虑兼容性

借鉴:https://blog.csdn.net/li123128/article/details/80650256

如果本文对你有帮助的话,请不要忘记给我点赞打call哦~o( ̄▽ ̄)do
有其他问题留言 over~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章