初识Generator和Async函数

从promise出现开始,JavaScript一直在致力于简化异步编程的流程,帮助开发者摆脱回调地狱的困境。
在ES6规范中引入新的概念Generator,由此node的框架koa迅速采用,并实现了co来帮助进行迭代,
而ES7中出现的Async函数更是将异步简化成了“同步”,可以让我们以接近编写同步代码的方式来编写异步代码(无需使用.then()或者回调函数),下面就将依次介绍这两种方法的区别与相似之处。

Generator

说到Generator我们首先来了解下Iterator(遍历器)这个同样是ES6的新概念。

Iterator(遍历器)它是一种接口,为各种不同的数据结构提供统一的访问机制。
任何数据结构只要部署Iterator接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)

ES6的文档里对Iterator进行了如上的定义,遍历器有以下三个作用:

  1. 为各种数据结构,提供一个统一的、简便的访问接口
  2. 使得数据结构的成员能够按某种次序排列
  3. ES6创造了一种新的遍历命令for...of循环,Iterator接口主要供for...of消费

Iterator的遍历过程分配以下几步:

  1. 创建一个指针对象,指向当前数据结构的起始位置。也就是说,遍历器对象本质上,就是一个指针对象。
  2. 第一次调用指针对象的next方法,可以将指针指向数据结构的第一个成员。
  3. 不断调用指针对象的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去做他该做的事情吧。。

原文地址:https://www.cnblogs.com/oldMrWhite/p/11559133.html

时间: 2024-08-15 21:15:21

初识Generator和Async函数的相关文章

初识async函数

为什么会出现async函数 首先从大的方面来说,出现async函数时为了解决JS编程中的异步操作,再往具体说就是为了对以往异步编程方法的一种改进,也有人说仅仅只是Generator 函数的语法糖,这个我暂时还没理解到那个程度,因为我对Generator还是一知半解,等理解透一些了会专门写篇文章记录一下的.回到async函数,它算是阶段性的解决了异步编程的问题,也就是说让我们无限接近了异步编程的最高境界,就是不用关心它是不是异步,一切都是同步编程的写法,那样可读性就很强了. 什么是async函数

async函数对比Generator函数

首先定义一个读取文件的异步函数 var readFile = function(fileName){ return new Promise((resolve,reject)=>{ fs.readFile(filename,(data,data)=>{ if(error) reject(error); resolve(data)l }) }) } var gen = function* (){ var f1 = yield readFile("./ect") var f2 =

js-ES6学习笔记-async函数(3)

1.await命令后面的Promise对象,运行结果可能是rejected,所以最好把await命令放在try...catch代码块中. 2.多个await命令后面的异步操作,如果不存在继发关系,最好让它们同时触发. // 写法一 let [foo, bar] = await Promise.all([getFoo(), getBar()]); // 写法二 let fooPromise = getFoo(); let barPromise = getBar(); let foo = await

js-ES6学习笔记-async函数

1.async 函数是 Generator 函数的语法糖.前文有一个 Generator 函数,依次读取两个文件. var fs = require('fs'); var readFile = function (fileName) { return new Promise(function (resolve, reject) { fs.readFile(fileName, function(error, data) { if (error) reject(error); resolve(dat

async函数

1.含义 async 函数是什么?一句话,它就是 Generator 函数的语法糖. 前文有一个 Generator 函数,依次读取两个文件. var fs = require('fs'); var readFile = function (fileName) { return new Promise(function (resolve, reject) { fs.readFile(fileName, function(error, data) { if (error) reject(error

async 函数--学习笔记一

含义: ES2017 标准引入了 async 函数,使得异步操作变得更加方便.async 函数是什么?一句话,它就是 Generator 函数的语法糖. 前文有一个 Generator 函数,依次读取两个文件. var fs = require('fs'); var readFile = function (fileName) { return new Promise(function (resolve, reject) { fs.readFile(fileName, function(erro

ECMAScript 6 学习(二)async函数

 1.什么是async函数 2.用法 2.1基本用法 3.语法 3.1返回promise对象 3.2promise状态的变化 3.3await命令 1.什么是async函数 async函数也是异步编程的解决方案. async函数是对是对generator函数进行了改进. var readFile = function (fileName) { return new Promise(function (resolve, reject) { fs.readFile(fileName, functio

ES6学习笔记(四)—— async 函数

await 是 async wait 的简写, 是 generator 函数的语法糖. async 函数的特点: async 声明一个方法是异步的,await 则等待这个异步方法执行的完成 asyncReadFile = async function () { var f1 = await readFile('/etc/fstab') var f2 = await readFile('/etc/shells') console.log(f1.toString()) console.log(f2.

ES6--Promise、Generator及async

ES6诞生以前,异步编程的方法,大概有如下四种:回调函数.事件监听.发布/订阅.Promise对象:ES6中,引入了Generator函数:ES7中,async更是将异步编程带入了一个全新的阶段. 十四.Promise对象 ? Promise,就是一个对象,用来传递异步操作的消息,避免了层层嵌套的回调函数.它代表了某个未来才会知道结果的事件(通常是一个异步操作),并且这个事件提供统一的API,可供进一步处理. (1)对象的状态不受外界影响.有三种状态:Pending(进行中).Resolved(