JavaScript 中的异步原理

来源:极链科技
作者:周哲
 
所谓“异步”  , 简单说就是一个任务分成两段 , 先执行第一段 , 然后转而执行其他任务 , 等做好了准备 , 再回过头执行第二段 。比如 , 有一个任务是读取文件进行处理 , 异步的执行过程就是下面这样 。
常见的浏览器无响应(假死) , 往往就是因为某一段 JAVA 代码长时间运行(比如死循环) , 导致整个页面卡在这个地方 , 其他任务无法执行 。
为了解决这个问题 , Java 语言将任务的执行模式分成两种:同步( Synchronous )和异步( Asynchronous ) 。
JavaScript 中的异步原理

文章插图
异步编程原理
Java 引擎负责解析 , 执行 Java 代码 , 但它并不能单独运行 , 通常都得有一个宿主环境 , 一般如浏览器或 Node 服务器 , 前文说到的单线程是指在这些宿主环境创建单一线程 , 提供一种机制 , 调用 Java 引擎完成多个 Java 代码块的调度 , 这种机制就称为事件循环( Event Loop ) 。
关于事件循环流程分解如下:
  1. 宿主环境为Java 创建线程时 , 会创建堆 (heap) 和栈 (stack)  , 堆内存储 Java 对象 , 栈内存储执行上下文;
  2. 栈内执行上下文的同步任务按序执行 , 执行完即退栈 , 而当异步任务执行时 , 该异步任务进入等待状态(不入栈) , 同时通知线程:当触发该事件时(或该异步操作响应返回时) , 需向消息队列插入一个事件消息;
  3. 当事件触发或响应返回时 , 线程向消息队列插入该事件消息(包含事件及回调);
  4. 当栈内同步任务执行完毕后 , 线程从消息队列取出一个事件消息 , 其对应异步任务(函数)入栈 , 执行回调函数 , 如果未绑定回调 , 这个消息会被丢弃 , 执行完任务后退栈;
  5. 当线程空闲(即执行栈清空)时继续拉取消息队列下一轮消息(next tick  , 事件循环流转一次称为一次 tick ) 。

JavaScript 中的异步原理

文章插图
很多的队列先后按顺序执行任务就形成了 Event
异步编程实现
1 :回调函数
优点:简单、容易理解和部署 。
缺点:不利于代码的阅读和维护 , 各个部分之间高度耦合( Coupling ) , 流程会很混乱 。
2 : Promise 对象
一个 promise 可能有三种状态:等待( pending )、已完成( fulfilled )、已拒绝( rejected ) ;
【JavaScript 中的异步原理】
JavaScript 中的异步原理

文章插图
resolve  , 接受一个成功值 , 传递给绑定的 fulfilled 回调函数中 。主要工作是将当前状态变为 fulfilled 状态 , 同时调用绑定的 fulfilled 回调函数 。
reject  , 接受一个失败信息 , 传递给绑定的 rejected 回调函数中 。主要工作是将当前状态变为 rejected 状态 , 同时调用绑定的 rejected 回调函数 。
then 方法返回一个 Promise。它有两个参数 , 分别为 Promise 在成功和失败情况下的回调函数 。
语法:
JavaScript 中的异步原理

文章插图

JavaScript 中的异步原理

文章插图
概括来说 promise 是对异步的执行结果的描述对象 。
3 : Generator
Generator 函数是 ES6 提供的一种异步编程解决方案  , 允许函数的暂停和恢复 。
异步任务的封装:
JavaScript 中的异步原理

文章插图
整个过程类似于 , 浏览器遇到标识符 * 之后 , 就明白这个函数是生成器函数 , 一旦遇到 yield 标识符 , 就会将以后的函数放入此异步函数之内 , 待异步返回结果后再进行执行 。
更深一步 , 从内存上来讲:
普通函数在被调用时 , JS 引擎会创建一个栈帧 , 在里面准备好局部变量、函数参数、临时值、代码执行的位置(也就是说这个函数的第一行对应到代码区里的第几行机器码) , 在当前栈帧里设置好返回位置 , 然后将新帧压入栈顶 。待函数执行结束后 , 这个栈帧将被弹出栈然后销毁 , 返回值会被传给上一个栈帧 。


推荐阅读