JS 异步编程:种类和原理
1 回调函数
ajax('api/user', function(err, data) {
if (err) {/* 发生错误 */}
ajax('api/userinfo', function(err, data) {
... /* 回调地狱 */
}
})
错误处理困难
- 回调函数发生错误时,无法使用 try-catch 来处理错误。由于事件循环机制,回调执行和 try-catch 不会位于同一步骤中;
- 因此,一般回调函数要手动传入 err,来处理错误,也就产生了大量样板代码
回调地狱
- 回调套回调,执行连续步骤非常棘手
代码耦合,维护困难
2 Promise
3 Generator
我们一步步引出 generator
。
for...of
for...of
在可迭代对象上创建一个迭代循环,调用自定义迭代钩子,并为每个不同属性的值执行语句。
可迭代对象?
String
, Array
, TypedArray
, Map
and Set
是所有内置可迭代对象, 因为它们的原型对象都有一个 @@iterator 方法。
- 可迭代协议:原型中都实现了一个方法
Symbol.iterator
,返回一个对象的无参函数,被返回对象符合迭代器协议。 - 迭代器协议:实现一个
next()
方法,返回{value: [any], done: [boolean]}
对象。
iterator.next() // 输出 {value: 1, done: false}
iterator.next() // 输出 {value: 2, done: false}
iterator.next() // 输出 {value: 3, done: false}
iterator.next() // 输出 {value: undefined, done: true}
// done 了之后,value = undefined
所以, 为什么不能用 for...of
来遍历一个对象呢? 原因很简单:JavaScript
的对象中没有实现一个这样的 iterator
。
Generator
来看看写法:
function* gen() {
yield 1
yield 2
yield 3
yield 4
}
console.dir(gen)
熟悉的配方,熟悉的味道~
Generator 有什么用?
它能够中断执行代码的特性,可以帮助我们来控制异步代码的执行顺序。
例如有两个异步的函数 A
和 B
, 并且 B
的参数是 A
的返回值,也就是说,如果 A
没有执行结束,我们不能执行 B
。
function* effect() {
const { param } = yield A()
const { result } = yield B(param)
console.table(result)
}
const iterator = effect()
iterator.next()
iterator.next() // 拿到result
执行两次 next()
得到结果,看起来很傻不是吗?有没有好的办法呢?(废话,肯定有啊) 假设你在每次执行 A()
/ B()
的请求结束之后,都会自动执行 next()
方法呢?
Generator + Promise
Generator
作为 ES6
中使用协程的解决方案来处理异步编程的具体实现,它的特点是:
Generator
中可以使用 yield
关键字配合实例 gen
调用 next()
方法,来将其内部的语句分割执行。简言之 : next()
被调用一次,则 yield
语句被执行一句,随着 next()
调用, yield
语句被依次执行。
所以,异步编程使用 Generator
和 Promise
来实现的原理是什么呢?
- 因为
Generator
本身yield
语句是分离执行的,所以我们利用这一点,在yield
语句中返回一个Promise
对象; - 首次调用
Generator
中的next()
后, 假设返回值叫result
,那么此时result.value
就是我们定义在yield
语句中的Promise
对象。
Generator实现的核心在于上下文的保存
,函数并没有真的被挂起,每一次yield,其实都执行了一遍传入的生成器函数,只是在这个过程中间用了一个context对象储存上下文,使得每次执行生成器函数的时候,都可以从上一个执行结果开始执行,看起来就像函数被挂起了一样
4 Async/Await
是 Generator
的语法糖。
和 Generator 的对比:
1、Generator 出现在ES2015中,async 出现在ES2017中,async 是 Generator 的语法糖;
2、执行方式不同,Generator 执行需要使用执行器(next()等方法);async 函数自带执行器,与普通函数的执行一样;
3、async 的语法语义更加清楚,async 表示异步,await 表示等待;而 Generator 函数的(*)号和 yield 的语义就没那么直接了;
4、Generator 中 yield 后面只能跟 Thunk 函数或 Promise 对象;而 async 函数中 await 后面可以是 promise 对象或者原始类型的值(会自动转为立即resovle的promise对象);
5、返回值不同,Generator 返回遍历器,相比于 async 返回 promise 对象操作更加麻烦。