参考文章:
- https://juejin.im/post/5c199c0ae51d452f6028a072
- https://blog.csdn.net/qq_31001889/article/details/80321892
- http://es6.ruanyifeng.com/#docs/generator-async
基础介绍
由于 js 的单线程,如果没有异步编程方法,则主线程将直接卡死,极大的影响代码执行效率,所以 js 结合事件循环,实现了以下异步编程的方法:
- 回调函数
- 事件监听(主要是前端的元素绑定监听事件,如 onClick)
- 发布/订阅
- Promise 对象
- generator 函数
- async/await
这里所说的异步为:将任务分为两部分,先执行其中一部分,然后执行其他任务,等第二部分做好了准备,再继续执行第二部分代码。简单的说就是不连续的执行。
异步实现的主要原理都是解决: js 主线程在执行代码的时候,遇到 I/O,计时器等异步耗时任务,将任务推到事件循环中,在事件循环遍历中,检测到任务状态完成后,要如何通知主线程继续执行内部代码。
核心需要理解的内容:
- 名词的定义:回调函数,发布/订阅,Promise 对象,协程,genetator 函数,参数求值策略,thunk 函数
- 每个方法的优缺点
- 什么是错误优先,为什么要将错误对象传入回调函数中
- Generator 函数为什么可以封装异步,是什么特性将它作为异步编程的完整解决方案
- js 是什么求值策略
- 协程执行顺序
- genetator 函数自动流程管理的 2 个实现方法
- 实现 发布/订阅(eventEmitter) , thunk 函数的自动流程管理 , promise 对象的自动流程管理
回调函数
示例代码:callback.js
回调函数是 js 异步编程最基础的实现方法,开发者可以通过将第二部分任务包装在一个函数内,等第二部分做好准备了,再执行这个函数。
优点:
- 代码容易理解
- 实现方法简单
缺点
- 代码混乱,不容易阅读和理解
- 代码耦合度高,多层回调容易造成回调地狱
这里需要提到的是,nodejs 约定回调函数的第一个参数为错误对象,第二参数为传入的值,这就是错误优先原则。原因为代码在执行第二部分的回调函数时候,原本的上下文环境已经结束,无法捕捉抛出的异常,所以将异常传入回调函数,进行处理。
发布/订阅
示例代码:eventEmitter.js
采用事件驱动的模式,将第二部分代码包装在消息订阅的回调函数内,当第二部分准备好了,则通过消息发送通知执行第二部分代码。nodejs 的核心就是采用了 v8 引擎实现了事件驱动,异步非堵塞 I/O。
优点:
- 代码耦合度低
缺点
- 代码分散,流程不容易理解
Promise 对象
示例代码:promise.js
Promise 为 es6 中的新语法,它的原理是将每个异步任务封装返回一个 Promise 对象,对象内管理着异步任务执行状态,当任务完成则通过 resolve 函数进行回调,失败则通过 reject 进行回调。
Promise 的写法是对回调函数进行改进,将原来回调函数的嵌套调用,修改为链式调用,使代码流程更容易理解。
优点:
- 链接调用,代码流程容易理解
- 代码耦合度低
缺点:
- 每个结果都需要进行 retudn,然后在 then 方法进行获得,造成代码量冗余
- 语法比较复杂
generator 函数
协程
协程为多个线程,协助完成异步任务,执行流程如下:
- 第一步,协程 A 开始执行
- 第二步,协程 A 执行到一半,进入暂停状态,执行权转接到协程 B
- 第三步,协程 B 过一段时间后,将执行权转接到 协程 A
- 第四部,协程 A 继续执行
这里的协程 A 就是异步任务
协程的 Generator 函数实现
示例代码:generator.js
Generator 函数就是协程在 es6 中的实现,最大的特点就是可以交出函数的执行权(暂停执行代码)。函数执行返回一个遍历器对象,对象内具有 next 方法,每次执行 next ,才会执行内部的 yiel 后的异步方法,返回一个对象。对象具有 2 个属性,value 代表异步返回结果,done 代表遍历是否完成。
Generator 函数可以暂停和恢复执行,这是它可以封装异步任务的根本原因。
Generator 函数的 2 个特性,使他可以作为异步编程的完整解决方案:
- 错误捕捉
- 数据交换
优点:
- 异步流程便是简洁
缺点:
- 无法自动进行流程管理
Generator 函数是一个异步操作容器,它的自动执行需要一种机制,当异步操作有了结果,需要交还代码执行权力。可以实现的两种方法如下:
- 回调函数。将异步操作封装成一个 thunk 函数,在 thunk 函数 内交还代码执行权。示例代码:thunk-callback-generator.js
- promise 对象。将异步操作封装成一个 promise 对象,用 then 方法交还代码执行权,示例代码:promise-generator.js
thunk 函数
编译器的 传名调用实现,将参数放到一个临时函数中,再将这个临时函数传入到函数体中,这个临时函数就被称为 thunk 函数
function a() {
console.log(...args);
}
function b(a) {
return function() {
a(...args);
};
}
b(a)(1, 2, 3);
参数求值策略
function f(a, b) {
return b;
}
f(3 * x * x - 2 * x - 1, x);
- 传值调用:参数在调用执行,就已经进行了计算,传入的为最终计算结果
- 传名调用:只有在使用到参数的时候,才会对参数进行计算。
js 是传值调用