• 从Iterator到async/await


    Generator和Async

    引言

    接触过Ajax请求的会遇到过异步调用的问题,为了保证调用顺序的正确性,一般我们会在回调函数中调用,也有用到一些新的解决方案如Promise相关的技术。

    在异步编程中,还有一种常用的解决方案,它就是Generator生成器函数。顾名思义,它是一个生成器,它也是一个状态机,内部拥有值及相关的状态,生成器返回一个迭代器Iterator对象,我们可以通过这个迭代器,手动地遍历相关的值、状态,保证正确的执行顺序。

    Iterator接口

    什么是Iterator接口

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

    Iterator的作用

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

    Iterator实现

    function makeIterator(array) {
      var nextIndex = 0;
      return {
        next: function() {
          return nextIndex < array.length ?
            {value: array[nextIndex++], done: false} :
            {value: undefined, 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 }
    

    原生具备Iterator接口的数据结构

    1. Array
    2. Map
    3. Set
    4. String
    5. TypedArray
    6. 函数的 arguments 对象
    7. NodeList 对象

    查看一下Map下面的所挂载的Iterator

    let map = new Map();
    console.log(map.__proto__);
    

    输出结果:

    clear:ƒ clear()
    constructor:ƒ Map()
    delete:ƒ delete()
    entries:ƒ entries()
    forEach:ƒ forEach()
    get:ƒ ()
    has:ƒ has()
    keys:ƒ keys()
    set:ƒ ()
    size:(...)
    values:ƒ values()
    Symbol(Symbol.iterator):ƒ entries()
    Symbol(Symbol.toStringTag):"Map"
    get size:ƒ size()
    __proto__:Object
    

    如何为Object部署一个Iterator接口

    function iteratorObject(obj){
        let keys = Object.keys(obj);
        let index = -1;
        return {
            next(){
                index++;
                return index<keys.length?{
                    value:obj[keys[index]],
                    key:keys[index],
                    done:false
                }:{
                    value:undefined,
                    key:undefined,
                    done:true
                }
            }
        }
    }
    let obj =  {a:1,b:2,c:3};
    let iter = iteratorObject(obj);
    console.log(iter.next());
    // {value: 1, key: "a", done: false}
    console.log(iter.next());
    // {value: 2, key: "b", done: false}
    console.log(iter.next());
    // {value: 3, key: "c", done: false}
    console.log(iter.next());
    // {value: undefined, key: undefined, done: true}
    

    通过上面的方法可以简单的为Object部署了一个Iterator接口。

    Generator函数

    Generator是ES6的新特性,通过yield关键字,可以让函数的执行流挂起,那么便为改变执行流程提供了可能。

    Generator语法

    dome:

    function * greneratorDome(){
        yield "Hello";
        yield "World";
        return "ending";
    }
    let grenDome = greneratorDome();
    console.log(grenDome)
    

    上面的代码中定义了一个Generator函数,获取到了函数返回的对象。下面是其输出结果。

    原型链:

    greneratorDome {<suspended>}
    __proto__:Generator
        __proto__:Generator
        constructor:GeneratorFunction {prototype: Generator, constructor: ƒ, Symbol(Symbol.toStringTag): "GeneratorFunction"}
        next:ƒ next()
        return:ƒ return()
        throw:ƒ throw()
        Symbol(Symbol.toStringTag):"Generator"
        __proto__:Object
    [[GeneratorStatus]]:"suspended"
    [[GeneratorFunction]]:ƒ* greneratorDome()
    [[GeneratorReceiver]]:Window
    [[GeneratorLocation]]:test.html:43
    [[Scopes]]:Scopes[3]
    

    通过上面的输出结果可以看的出来,沿着原型链向上查找就存在一个next方法,这个方法与Iterator接口返回的结果是大同小异的。

    继续延续dome代码,并使用next方法向下执行。

    function * greneratorDome(){
        yield "Hello";
        yield "World";
        return "Ending";
    }
    let grenDome = greneratorDome();
    console.log(grenDome.next());
    // {value: "Hello", done: false}
    console.log(grenDome.next());
    // {value: "World", done: false}
    console.log(grenDome.next());
    // {value: "Ending", done: true}
    console.log(grenDome.next());
    // {value: undefined, done: true}
    

    在最开始的地方有提到过Generator函数,最后返回的是一个Iterator对象,这也就不难理解了。

    异步的Generator

    dome

    function a (){
        setTimeout(() => {
            alert("我是后弹出");
        },1000)
    }
    function b (){
        alsert("我是先弹出");
    }
    function * grenDome (){
        yield a();
        yield b();
    }
    let gren = grenDome();
    gren.next();
    gren.next();
    // 输出结果
    // 我是先弹出
    // 我是后弹出
    

    结合Promise

    function a (){
        return new Promise((resolve,reject) => {
            setTimeOut(() => {
                console.log(1)
                resolve("a");
            })
        })
    }
    function b (){
        return new Promise((resolve,reject) => {
             console.log(2)
            resolve("b");
        })
    }
    function * grenDome (){
        yield a();
        yield b();
        return new Promise((resolve,reject) => {
            resolve("grenDome内部")
        })
    }
    let gren = grenDome();
    
    // console.log(gren.next())
    // {value: Promise, done: false}
    // console.log(gren.next())
    // {value: Promise, done: false}
    // console.log(gren.next())
    // {value: Promise, done: true}
    // console.log(gren.next())
    // {value: undefined, done: true}
    
    gren.next().value.then((res) => {
        console.log(res);
        // a函数
    })
    gren.next().value.then((res) => {
        console.log(res);
        // b函数
    })
    gren.next().value.then((res) => {
        console.log(res);
        // grenDome内部
    })
    // 输出结果
    // a
    // b
    // grenDome内部
    

    在上面的代码中有一点是需要注意的,在grenDome函数里面最后return出去了一个Promise,但是在输出的时候虽然done属性已经为true但是value里面仍然会存有一个promise对象,实际上done表示的是对应yield关键字的函数已经遍历完成了。

    Async/Await

    Async/awaitJavascript编写异步程序的新方法。以往的异步方法无外乎回调函数和Promise。但是Async/await建立于Promise之上,换句话来说使用了Generator函数做了语法糖。

    async函数就是隧道尽头的亮光,很多人认为它是异步操作的终极解决方案。

    什么是Async/Await

    async顾名思义是“异步”的意思,async用于声明一个函数是异步的。而await从字面意思上是“等待”的意思,就是用于等待异步完成。并且await只能在async函数中使用。

    Async/Await语法

    function timeout(ms) {
      return new Promise((resolve) => {
        setTimeout(resolve, ms);
      });
    };
    async function asyncPrint(value, ms) {
      await timeout(ms);
      console.log(value);
    };
    asyncPrint('hello world',2000);
    // 在2000ms之后输出`hello world`
    

    返回Promse对象

    通常asyncawait都是跟随Promise一起使用的。为什么这么说呢?因为async返回的都是一个Promise对象同时async适用于任何类型的函数上。这样await得到的就是一个Promise对象,如果不是Promise对象的话那async返回的是什么就是什么。

    async function f() {
      return 'hello world';
    }
    f().then(v => console.log(v));
    // hello world
    

    async函数返回一个Promise对象,可以使用then方法添加回调函数。当函数执行的时候,一旦遇到await就会先返回,等到异步操作完成,再接着执行函数体内后面的语句。

    function a(){
        return new Promise((resolve,reject) => {
            console.log("a函数")
            resolve("a函数")
        })
    }
    function b (){
        return new Promise((resolve,reject) => {
            console.log("b函数")
            resolve("b函数")
        })
    }
    async function dome (){
        let A = await a();
        let B = await b();
        return Promise.resolve([A,B]);
    }
    dome().then((res) => {
        console.log(res);
    });
    

    执行机制

    前面已经说过await是等待的意思,之后等前面的代码执行完成之后才会继续向下执行。

    function a(){
        return new Promise((resolve,reject) => {
            resolve("a");
            console.log("a:不行")
        })
    }
    function b (){
        return new Promise((resolve,reject) => {
            resolve("b");
            console.log("b:不行");
        })
    }
    async function dome (){
        await a();
        await b();
        console.log("虽然我在后面,但是我想要先执行可以么?")
    }
    dome();
    // 输出结果
    // a:不行
    // b:不行
    // 虽然我在后面,但是我想要先执行可以么?
    

    另外一个列子

    function timeout1(ms) {
      return new Promise((resolve) => {
        setTimeout(() => {
            console.log("timeout1")
            resolve();
        },ms);
      });
    };
    function timeout2(ms) {
      return new Promise((resolve) => {
        setTimeout(() => {
            console.log("timeout2");
            resolve();
        },ms);
      });
    };
    async function asyncPrint() {
      await timeout1(1000);
      await timeout2(2000);
    };
    asyncPrint().then((res) => {
        console.log(res);
    }).catch((err) => {
        console.log(err)
    })
    // 1s 后输出timeout1
    // 3s 后输出timeout2
    // undefined
    

    async、await错误处理

    JavaScript异步请求肯定会有请求失败的情况,上面也说到了async返回的是一个Promise对象。既然是返回一个Promise对象的话那处理当异步请求发生错误的时候我们就要处理reject的状态了。

    在Promise中当请求reject的时候我们可以使用catch。为了保持代码的健壮性使用async、await的时候我们使用try catch来处理错误。

    async function f() {
      await Promise.reject('出错了');
      await Promise.resolve('hello world');
    }
    
    async function b() {
        try {
          await f();
        } catch(err) {
         console.log(err);
        }
    }
    b();
    //  出错了
    

    总结

    # Iterator接口

    遍历器对象除了具有next方法,还可以具有return方法和throw方法。如果你自己写遍历器对象生成函数,那么next方法是必须部署的,return方法和throw方法是否部署是可选的。

    Es6提供很多API都是基于Iterator接口,比如解构,for...of循环,拓展运算等。

    # Generator函数

    调用Generator函数,返回一个遍历器对象,代表Generator函数的内部指针。以后每次调用遍历器对象的next方法,就会返回一个有着valuedone两个属性的对象。
    value属性表示当前的内部状态的值,是yield语句后面那个表达式的值;done属性是一个布尔值,表示是否遍历结束

    # Async/Await

    Async/await是近些年来JavaScript最具革命性的新特性之一。他让读者意识到使用Promise存在的一些问题,并提供了自身来代替Promise的方案。他使得异步代码变的不再明显,我们好不容易已经学会并习惯了使用回调函数或者then来处理异步。

  • 相关阅读:
    java 全组合 与全排列
    (转)java +libsvm 安装与测试:
    ictclas4j 分词工具包 安装流程
    (转)超详细单机版搭建hadoop环境图文解析
    HTML+CSS 滚动条样式自定义
    HTML+CSS之iframe
    思维导图工具 & 原型设计工具
    js setInterval 启用&停止
    jquery 获取 checkbox 的 checked 状态问题
    几个实用的 jQuery 插件
  • 原文地址:https://www.cnblogs.com/aaron---blog/p/11105566.html
Copyright © 2020-2023  润新知