异步编程原理
JavaScript 引擎负责解析,执行 JavaScript 代码,但它并不能单独运行,通常都得有一个宿主环境,一般如浏览器或 Node 服务器,前文说到的单线程是指在这些宿主环境创建单一线程,提供一种机制,调用 JavaScript 引擎完成多个 JavaScript 代码块的调度,这种机制就称为事件循环( Event Loop )。
关于事件循环流程分解如下:
宿主环境为 JavaScript 创建线程时,会创建堆 (heap) 和栈 (stack) ,堆内存储 JavaScript 对象,栈内存储执行上下文;栈内执行上下文的同步任务按序执行,执行完即退栈,而当异步任务执行时,该异步任务进入等待状态(不入栈),同时通知线程:当触发该事件时(或该异步操作响应返回时),需向消息队列插入一个事件消息;当事件触发或响应返回时,线程向消息队列插入该事件消息(包含事件及回调);当栈内同步任务执行完毕后,线程从消息队列取出一个事件消息,其对应异步任务(函数)入栈,执行回调函数,如果未绑定回调,这个消息会被丢弃,执行完任务后退栈;当线程空闲(即执行栈清空)时继续拉取消息队列下一轮消息(next tick ,事件循环流转一次称为一次 tick )。
很多的队列先后按顺序执行任务就形成了 Event
异步编程实现
回调函数
优点:简单、容易理解和部署。
缺点:不利于代码的阅读和维护,各个部分之间高度耦合( Coupling ),流程会很混乱。
Promise 对象
一个 promise 可能有三种状态:等待( pending )、已完成( fulfilled )、已拒绝( rejected ) ;
resolve,接受一个成功值,传递给绑定的 fulfilled 回调函数中。主要工作是将当前状态变为 fulfilled 状态,同时调用绑定的 fulfilled 回调函数。
reject,接受一个失败信息,传递给绑定的 rejected 回调函数中。主要工作是将当前状态变为 rejected 状态,同时调用绑定的 rejected 回调函数。
then 方法返回一个 Promise。它有两个参数,分别为 Promise 在成功和失败情况下的回调函数。
语法:
概括来说 promise 是对异步的执行结果的描述对象。
Generator
Generator 函数是 ES6 提供的一种异步编程解决方案 ,允许函数的暂停和恢复。
异步任务的封装:
整个过程类似于,浏览器遇到标识符 * 之后,就明白这个函数是生成器函数,一旦遇到 yield 标识符,就会将以后的函数放入此异步函数之内,待异步返回结果后再进行执行。
更深一步,从内存上来讲:
普通函数在被调用时,JS 引擎会创建一个栈帧,在里面准备好局部变量、函数参数、临时值、代码执行的位置(也就是说这个函数的第一行对应到代码区里的第几行机器码),在当前栈帧里设置好返回位置,然后将新帧压入栈顶。待函数执行结束后,这个栈帧将被弹出栈然后销毁,返回值会被传给上一个栈帧。
当执行到 yield 语句时, Generator 的栈帧同样会被弹出栈外,但 Generator 在这里耍了个花招 —— 它在堆里保存了栈帧的引用(或拷贝)!这样当 it.next 方法被调用时, JS 引擎便不会重新创建一个栈帧,而是把堆里的栈帧直接入栈。因为栈帧里保存了函数执行所需的全部上下文以及当前执行的位置,所以当这一切都被恢复如初之时,就好像程序从原本暂停的地方继续向前执行了。
而因为每次 yield 和 it.next 都对应一次出栈和入栈,所以可以直接利用已有的栈机制,实现值的传出和传入。