Iterator、Generator、async、await 异步编程

Iterator、Generator、async、await 异步编程

参考ECMAScript 6 入门

Iterator 遍历器

说明

Iterator(遍历器、迭代器)是一种接口,他为不同的数据结构提供了统一的访问机制(即 for-of循环),任何数据结构只要部署 Iterator=接口,就可以完成遍历操作(即一次处理该数据结构的所有成员)

主要作用

  1. 为各种数据结构提供统一、简便的访问借口
  2. 使得数据结构的成员能够按照某种次序排列
  3. 提供给 for-of 循环使用

一个基本的迭代器的构成

从规范的遍历过程来看

  1. 迭代器对象创建时,实际是创建一个指针对象,指向当前数据结构的起始位置
  2. 第一次调用迭代器对象的 next() 方法,指针指向数据结构的第一个成员
  3. 不断调用 next() 方法 指针依次指向下一个成员,直到指向数据结构的末尾

每一次调用 next() 方法都会返回当前成员的信息,包括 value:值;done:true|false 表示遍历是否结束

    const a = [12, 223, 34, 54];
    const iter = convertToIterator(a);

    console.log(iter.next()); // {value: 12, done: false}
    console.log(iter.next()); // {value: 223, done: false}
    console.log(iter.next()); // {value: 34, done: false}
    console.log(iter.next()); // {value: 54, done: false}
    console.log(iter.next()); // {value: undefined, done: true}

    function convertToIterator(arr) {
        let idx = 0;

        return {
            next: function () {
                return idx < arr.length ? 
                    {value: arr[idx++], done: false} : 
                    {value: undefined, done: true};
            }
        }
    }

默认的 Iterator 接口

一种数据结构只要部署了 Iterator 接口,就可以成为‘可遍历的’, ES6 的 Iterator 接口部署在数据结构的 Symbol.iterator 上,这个属性本省是一个函数,调用这个函数就会返回一个遍历器对象,遍历器对象具有 next() 方法

控制台查看查看

    console.log(Array.prototype);

这里写图片描述

Symbol 对象简介

ES5 中对象的属性名都是字符串,如果给对象添加新方法的名称与旧的相同,就会将之前的属性覆盖掉,Symbol 是 ES6 引入的一种新的原始数据类型(有点类似于字符串的数据类型),表示独一无二的值

Symbol.iterator 是内置的Symbol 值(Symbol(Symbol.iterator))指向对象的默认遍历器

更多 Symbol 资料查看这里

基本结构
    const obj = {
        [Symbol.iterator] : function () {
            return {
              next: function () {
                return {
                  value: 1,
                  done: true
                };
              }
            };
        }
    };
具备 Iterator 接口的数据结构

Array | Map | Set | String | TypedArray | 函数的 arguments

调用 Iterator 接口的方法 for-of | 扩展运算符... | 解构赋值 | Array.form

参考了 Iterator 和 for…of 循环

    let arr = ['a', 'b', 'c'];
    let iter = arr[Symbol.iterator]();

    iter.next() // { value: 'a', done: false }
    iter.next() // { value: 'b', done: false }
    iter.next() // { value: 'c', done: false }
    iter.next() // { value: undefined, done: true }

Generator 函数

Generator 函数是 ES6 提供的一种异步编程的解决方案,语法行为与传统函数完全不同,可以将其理解为一个状态机,内部封装了多个内部状态,执行 Generator 函数会返回一个遍历器对象,返回的遍历器对象可以依次遍历 Generator 函数内部的每一个状态

一个 Generator 函数的基本构成

与普通函数相比

  • Generator 函数 function 关键字后要加 * function* name(){};
  • 函数体内部使用 yield 表达式,定义不同的内部状态,最后一个 return 表示返回最后一个状态
  • Generator 函数调用与普通函数一样,不同的是 Generator 函数调用后并不执行,返回的也不是函数结果,而是一个指向内部状态的指针,也就是遍历器对象
  • 每次通过调用遍历器对象的next() 方法,使指针移动到下一个状态,直到最后一个 return 的状态
  • Generator 函数是分段执行的, yield 表达式是暂停执行的标记,而 next 方法可以恢复执行
    function* generator () {
        yield 'first';
        yield 'sec';
        return 'finish';
    }

    const a = generator();
    console.log(a);
    console.log(a.next()); // {value: "first", done: false}
    console.log(a.next()); // {value: "sec", done: false}
    console.log(a.next()); // {value: "finish", done: true}
    console.log(a.next()); // {value: undefined, done: true}

yield 表达式

yield 表达式是 Generator 函数内部的暂停标志

  1. 调用 Generator 函数,函数体内代码不会执行
  2. 调用 next() 方法后,函数体内代码顺序执行,直到遇到 yield 表达式(yield 111 + 1;),执行完此表达式,表达式的值就是返回对象的 value
  3. yield 表达式与 return 相似,都能返回后边表达式的值,return 在一个函数里只能执行一次,yield 可以执行很多次,
  4. yield 表达式(a)如果在另外一个表达式中(b),必须放在括号中,而且 yield 表达式a 执行后的返回值与表达式 b 暂时不会执行,会等到下一次 next 在执行
    function* generator () {
        console.log('1');
        yield 111 + 1;
        console.log('2');
        yield 222 + 2;
        console.log('3');
        return 33 + 3;
    }

    const a = generator(); // 什么都没有打印
    console.log(a);
    const fir = a.next(); // 1
    console.log(fir); // {value: 112, done: false}
    a.next(); // 2

next 方法的参数

yield 表达式本身没有返回值,或者说总是返回 undefined next 方法可以带一个参数,此参数会被当做 上一个 yield 表达式的返回值,一般第一次调用 next()方法不会输入值,只有第二次传递的参数才是有效的

    function* gerenator() {
        const x = 30;
        const a = yield x;
        return a + 10;
    }

    const test = gerenator();

    const val = test.next().value;

    const final = test.next(val + 50);

    console.log(final); // {value: 90, done: true}

for-of 循环

for…of循环可以自动遍历 Generator 函数时生成的Iterator对象,且此时不再需要调用next方法

除了for…of循环以外,扩展运算符(…)、解构赋值和Array.from方法内部调用的,都是遍历器接口。这意味着,它们都可以将 Generator 函数返回的 Iterator 对象,作为参数


    function* gerenator() {
        const x = 30;
        const a = yield x;
        const b = yield a;
        return b + 10;
    }

    const test = gerenator();

    // for-of 循环
    for (let o of test) {
        console.log(o);
        // 30 undefined
    }

    // 展开操作符
    console.log([...test]); // [30, undefined]

    // Array.from()
    console.log(Array.from(test)); // [30, undefined]

    // 解构赋值
    let [a, b] = test;
    console.log(a, b); // 30 undefined

Generator.prototype.throw()

Generator 函数的遍历器对象都由一个 throw() 方法,可以在函数体外抛出错误,然后在函数体内捕获

Generator 函数体外抛出的错误,可以在函数体内捕获;反过来,Generator 函数体内抛出的错误,也可以被函数体外的catch捕获。

    var g = function* () {
      try {
        yield;
      } catch (e) {
        console.log('内部捕获', e);
      }
    };

    var i = g();
    i.next();

    try {
      i.throw('a');
      i.throw('b');
    } catch (e) {
      console.log('外部捕获', e);
    }
    // 内部捕获 a
    // 外部捕获 b

Generator.prototype.return()

return 方法 用来终结遍历 Generator 函数,如果 return() 有参数,则返回参数的值

    function* gen() {
      yield 1;
      yield 2;
      yield 3;
    }

    var g = gen();

    console.log(g.next());       // { value: 1, done: false }
    console.log(g.return('foo')); // { value: "foo", done: true }
    console.log(g.return('foo')); // { value: undefined, done: true }
    console.log(g.next());        // { value: undefined, done: true }

    function* test() {
        yield 1;
      yield 2;
      yield 3;
    }

    var t = test();

    console.log(t.next()); // { value: 1, done: false }
    console.log(t.return()); // { value: undefined, done: true }

yield* 表达式

用于在一个 Generator 函数内部调用另外一个 Generator 表达式

  • Generator 函数内部直接执行 foo() 外部函数不会得到返回值
  • Generator 函数内部执行 yield foo() 外部函数会得到一个遍历器对象
  • Generator 函数内部执行 yield* foo() 外部函数会使用这个遍历器
    function* foo() {
        yield 'a';
        yield 'b';
    }

    function* bar1() {
        yield 'x';
        yield foo();
        yield 'y';
    }

    for (let o of bar1()) {
        console.log(o); 
        /*
            x
            Generator {_invoke: function}
            y
        */ 
    }

    function* bar2() {
        yield 'x';
        yield* foo();
        yield 'y';
    }

    for (let o of bar2()) {
        console.log(o); 
        /*
            x
            a
            b
            y
        */ 
    }

作为对象属性的 Generator 函数

    // 简写
    let obj = {
        * myGeneratorMethod() {

        }
    };

    // 等价于
    let obj = {
        myGeneratorMethod: function* () {

        }
    };

注意事项

  1. Generator 函数不是构造函数 不能使用 new 关键字创建对象
  2. Generator 函数总是返回一个遍历器,而不是一般构造函数的 this 不可以像构造函数一样在函数内部 通过 this 定义属性,但是可以在 函数的 prototype 上定义方法

javascript 传统的异步编程

异步就是指一个任务不是连续完成的,先执行一部分,转而做其他任务,等到做好准备再回来执行接下的部分

  • 回调函数
  • 事件监听
  • 发布/订阅
  • Promise

协程

‘协程’的意思是多个线程相互协作,大致流程如下

  • 协程 A 开始执行
  • 协程 A 执行到一半,进入暂停,执行权转移到协程 B
  • 一段时间后 协程 B 交还执行权给 A
  • 协程 A 继续执行

Generator 函数是携程在 ES6 的实现,最大的特点就是可以交出函数的执行权,整个 Generator 函数就是一个封装的异步任务,或者说是异步任务的容器,异步操作需要暂停的地方都用 yield 语句注明

    function change() {
        return 'change';
    }

    function* test() {
        const a = yield change(); // 执行权交给 change 函数
        yield a + ' something';
    }

    const ts = test();
    const ret = ts.next();
    console.log(ret.value); // change
    console.log(ts.next(ret.value).value); // change something

Generator 函数的数据交换和错误处理

  • 向外输出数据 通过 next() 返回值的 value 属性
  • 向内注入数据 通过 next(sth) 方法添加参数
  • Generator 函数内可以部署错误处理代码,捕获函数体外抛出的错误(generator.throw())

async 函数

async 函数由 ES2017 引入,为了更加方便异步操作,它其实就是 Generator 函数的语法糖

async 函数 与 Generator 函数的差异

  • 内置执行器,Generator 函数执行必须靠执行器或者调用 next() 方法,而 async 函数自带执行器,async 函数的调用与普通函数一样
  • 更好的语义:显而易见 async(异步)await(等待)这样的组合更容易表示
  • await 命令后边可以是 Promise 对象或者原始类型的值(原始类型的值,会等同于同步操作)
  • async 函数返回值是 Promise 类型的可以用 then 方法指定下一步操作
    async function timeout(ms) {
        await new Promise(function (resolve) {
            setTimeout(resolve, ms);
        })
    }

    async function asyncPrint (value, ms) {
        await timeout(ms);
        console.log(value);
    }

    asyncPrint('hahaha', 1000);

async 函数的多种使用形式

    // 函数声明
    async function fn () {}

    // 函数表达式
    const fn = async function () {}

    // 对象的方法
    const obj = {
        async fn () {}
    }

    // Class 方法
    class Fn {
        async getFn() {

        }
    }

    // 箭头函数
    const fn = async () => {};

async 函数的返回值

async 函数返回一个 Promise 对象,这个对象可以调用 then() 方法 进行下一步的操作,但是如果需要上一步的返回值则需要在 async 函数内 return

async函数内部抛出错误,会导致返回的 Promise 对象变为reject状态。抛出的错误对象会被catch方法回调函数接收到

    const obj = {
        name: 'obj name',
        age: 'obj age',
        async timeout(ms) {
            const age = await new Promise((resolve) => {
                setTimeout(() => {
                    resolve(this.age);
                }, ms)
            })
            // 返回给下一步的参数
            return this.name + ' ' + this.age;
        }
    }

    async function asyncPrint (value, ms) {
        const info = await obj.timeout(ms);
        console.log(value, info); 

        // 抛出错误
        throw new Error('wrong');
    }

    asyncPrint('hahaha', 1000) // hahaha obj name obj age
        .then(res => {
            console.log('right', res);
        })
        .catch(err => {
            console.log(err); // 错误被捕获 Error: wrong
        })

一个 async 函数返回的 Promise 对象,必须等到内部素有的 await 执行完成后,才会发生状态改变,除非遇到 return 语句 或者抛出错误

await 命令

  1. 正常情况下 await 命令后是一个 Promise 对象,如果不是,会被转变成一个立即 resolve 的 Promise 对象
  2. await 命令后边的 Promise 对象如果为 reject 状态,则 reject 的参数会被 catch 方法的回调函数接收
  3. 只要一个 await 语句后面的 Promise 变为 reject 那么整个 async 函数都会中断执行
  4. 通过 try-catch 结构可以避免一个异步操作失败影响后边异步操作执行
    // 1. 正常情况下 await 命令后是一个 Promise 对象,如果不是,会被转变成一个立即 resolve 的 Promise 对象
    async function fn() {
        return await 222;
    }

    fn().then(res => console.log(res)); // 222;

    // 2. await 命令后边的 Promise 对象如果为 reject 状态,则 reject 的参数会被 catch 方法的回调函数接收
    // 3. 只要一个 await 语句后面的 Promise 变为 reject 那么整个 async 函数都会中断执行
    async function fn() {
        await Promise.reject('wrong');
        return await 222; // 不会执行
    }

    fn()
        .then(res => console.log(res)) // 不执行
        .catch(err => console.log('error', err)); // error wrong

    // 4. 通过 try-catch 结构可以避免一个异步操作失败影响后边异步操作执行
    async function fn() {
        try {
            await Promise.reject('wrong');
        } catch(e) {
            console.log('catch error', e); // catch error wrong
        }
        return await 222;
    }

    fn()
        .then(res => console.log(res)) // 222;
        .catch(err => console.log('error', err)); // 不执行1
    // 在可能会抛出错误的Promise 后边接上 catch() 也可以达到同样的效果
    async function fn() {
        await Promise.reject('wrong').catch(err => console.log('catch', err)); // catch wrong
        return await 222;
    }

    fn()
        .then(res => console.log(res)) // 222;
        .catch(err => console.log('error', err)); // 不执行

注意事项

1. 如果了两个异步操作没有互相依赖关系,可以让他们同时触发
    // 写法一
    let [foo, bar] = await Promise.all([getFoo(), getBar()]);

    // 写法二
    let fooPromise = getFoo();
    let barPromise = getBar();
    let foo = await fooPromise;
    let bar = await barPromise;
2. await 命令只能用在 async 函数中,如果 用在普通函数中就会报错
    async function fn() {
        const arr = [11, 232, 33];

        arr.forEach((item) => {
            await new Promise((resolve) => {
                resolve(item)
            })
        })
        return await 222;
    }
    fn()
        .then(res => console.log(res)) // 报错

更多文章

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值