初识Generator和Async函数
Posted oldmrwhite
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了初识Generator和Async函数相关的知识,希望对你有一定的参考价值。
从promise出现开始,javascript一直在致力于简化异步编程的流程,帮助开发者摆脱回调地狱的困境。
在ES6规范中引入新的概念Generator,由此node的框架koa迅速采用,并实现了co来帮助进行迭代,
而ES7中出现的Async函数更是将异步简化成了“同步”,可以让我们以接近编写同步代码的方式来编写异步代码(无需使用.then()或者回调函数),下面就将依次介绍这两种方法的区别与相似之处。
Generator
说到Generator我们首先来了解下Iterator(遍历器)这个同样是ES6的新概念。
Iterator(遍历器)它是一种接口,为各种不同的数据结构提供统一的访问机制。
任何数据结构只要部署Iterator接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)
ES6的文档里对Iterator进行了如上的定义,遍历器有以下三个作用:
- 为各种数据结构,提供一个统一的、简便的访问接口
- 使得数据结构的成员能够按某种次序排列
- ES6创造了一种新的遍历命令for...of循环,Iterator接口主要供for...of消费
Iterator的遍历过程分配以下几步:
- 创建一个指针对象,指向当前数据结构的起始位置。也就是说,遍历器对象本质上,就是一个指针对象。
- 第一次调用指针对象的next方法,可以将指针指向数据结构的第一个成员。
- 不断调用指针对象的next方法,直到它指向数据结构的结束位置。
我们可以对是否结束进行控制,next需要返回一个对象,里面包含一个value和一个布尔类型的done,通过done是否等于true来表示遍历是否结束
```
var it = makeIterator(['a', 'b']);
it.next() // { value: "a", done: false }
it.next() // { value: "b", done: false }
it.next() // { value: undefined, done: true }
function makeIterator(array) {
var nextIndex = 0;
return {
next: function() {
return nextIndex < array.length ?
{value: array[nextIndex++], done: false} :
{value: undefined, done: true};
}
};
}
```
在ES6中,有些数据结构原生具备Iterator接口(比如数组),即不用任何处理,就可以被for...of循环遍历,有些就不行(比如对象)。原因在于,这些数据结构原生部署了Symbol.iterator属性,另外一些数据结构没有。凡是部署了Symbol.iterator属性的数据结构,就称为部署了遍历器接口。调用这个接口,就会返回一个遍历器对象。
我们可以自己对一个数据结构添加Symbol.iterator属性来实现自定义的数据结构的遍历,下面是一个实现生成一个简单地可遍历的链表的例子。
```
// 生成一个链表的节点
function ConstructLink(val,isHead = false){
this.value = val
this.next = null
this.isHead = isHead
}
ConstructLink.prototype[Symbol.iterator] = function(){
let current = this
function next(){
if(current){
// 过滤头结点的遍历
if(current.isHead){
current = current.next
}
const value = current.value
// 每次调用指针向后移动
current = current.next
return {
done: false,
value
}
}else{
return {
done: true
}
}
}
const iterator = {
next:next
}
return iterator
}
// 将数组转化成可遍历的链表
function LinkList(array = []){
const headLink = new ConstructLink('',true)
array.reduce((accumulator,item)=>{
const newLinkItem = new ConstructLink(item)
accumulator.next = newLinkItem
return newLinkItem
},headLink)
return headLink
}
const list = LinkList([1,2,3])
for(let i of list){
console.log(i)
}
// 1
// 2
// 3
```
很多人会开始疑问,Generator和Iterator有什么关系,为什么要话如此大的篇幅来讲述Generator,下面来看一个简单地的Generator的例子
```
function* helloWorldGenerator() {
yield 'hello';
yield 'world';
return 'ending';
}
var hw = helloWorldGenerator();
console.log(hw.next())
// { value: 'hello', done: false }
console.log(hw.next())
// { value: 'world', done: false }
console.log(hw.next())
// { value: 'ending', done: true }
```
通过Generator的调用我们看到了一个熟悉的结构,Iterator的return结构和next()函数。
Generator函数的调用方法与普通函数一样,也是在函数名后面加上一对圆括号。不同的是,调用Generator函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象,也就是上面介绍的遍历器对象(Iterator Object)。
由于Generator也是一个遍历器对象,那么也是可以被for...of进行遍历的。
```
function *myGenerator(){
yield 'hello'
yield 'world'
return 'ending'
}
const demo = myGenerator()
for(let item of demo){
console.log(item)
}
// hello
// world
```
yield句本身没有返回值,或者说总是返回undefined。next方法可以带一个参数,该参数就会被当作上一个yield语句的返回值。
```
function *paramGenerator(){
const param1 = yield 1
console.log(param1)
const param2 = yield 2
console.log(param2)
}
const demo2 = paramGenerator()
demo2.next('first param')
demo2.next('second param')
demo2.next('final param')
// second param
// final param
```
这里面突然就出现了一个比较奇怪的地方,第一次调用的时候并没有打印出期望的“frist param”,而是直接从第二次调用的时候才进行打印
由于Generator函数内部在遇到yield的时候会暂停,所以第一次调用next(‘firast param‘)
的时候会在赋值前停顿,在第二次调用next(‘second param‘)
的时候才会继续赋值操作,并将第二次的参数赋值给param1
这个变量。
经由这个特性我们就会想到,可以用yield来处理promise函数,当promise resolve的时候触发next()
,并将异步获取的数据作为next()
函数的参数,从而实现同步执行。
function run(gen){
gen = gen() // 初始化Iterator结构
return next(gen.next())
/**
* generator的执行器
* @param {*} param 调用Iterator.next()返回的对象
*/
function next({done,value}){
return new Promise(resolve => {
if(done){
resolve(value)
}else{
// 执行每一个异步的promise,并将数据通过Iterator.next()的参数返回
value.then((data) => {
// 继续递归执行,并传递初次调用传入的reslove
next(gen.next(data)).then(resolve)
})
}
})
}
}
/**
* 模拟实现一个异步的promise函数
*/
function mockAsyncPromiseFunc(value){
return new Promise(resolve => {
setTimeout(() => { resolve(value) },0)
})
}
function *getTotalValue(){
const value1 = yield mockAsyncPromiseFunc(1)
console.log(value1)
const value2 = yield mockAsyncPromiseFunc(2)
console.log(value2)
return value1 + value2
}
run(getTotalValue).then(returnData => {
console.log(returnData)
})
// 1
// 2
// 3
我们通过构造一个Generator的执行器就能在内部实现同步的操作,但是这样可能会因为业务需求的不同而需要构造出多个对应的执行器,能否有一个更加方便的方法来实现同步呢?ES7规范中的Async函数帮助我们解决了这个问题
Async函数
当我们用Async函数来对上面的例子进行重写,一切就会变得简单起来
function *getTotalValue(){
const value1 = yield mockAsyncPromiseFunc(1)
console.log(value1)
const value2 = yield mockAsyncPromiseFunc(2)
console.log(value2)
return value1 + value2
}
async function getTotalValueAsync(){
const value1 = await mockAsyncPromiseFunc(1)
console.log(value1)
const value2 = await mockAsyncPromiseFunc(2)
console.log(value2)
return value1 + value2
}
console.log(getTotalValueAsync())
// 1
// 2
// 3
这样看上去,好像我们从Generator/yield换到async/await只需要把*都改为async,yield都改为await就可以了。
所以很多人都直接拿Generator/yield来解释async/await的行为,但这会带来如下几个问题
- Async 函数只能用来处理Promise,而Generator则可以处理任何函数
Async 函数只会返回Promise
Async函数并不能完全取代Generator,但是无疑,Async函数是一个非常简便的解决Promise的方案。可以让我们以更加直观的方式来处理异步。结论
Generator与async function都是返回一个特定类型的对象:- Generator返回
{ value, done }
类型的对象 async function 返回Promise
Generator属于一种生成器,用来生成Iterator对象,在配合co的时候能用来解决异步
而async则是为了更简洁的使用Promise而提出的语法,专门为了解决异步而提出现在已经是2019年了,async也是用了好久,就让Generator去做他该做的事情吧。。
以上是关于初识Generator和Async函数的主要内容,如果未能解决你的问题,请参考以下文章
async和Generator是一对好基友,且async是主动型
每天十分钟学好ES6--async和Generator是一对好基友
每天十分钟学好ES6--async和Generator是一对好基友