Iterator、Generator、async、await 异步编程

来源:互联网 发布:淘宝网的女款高端衬衣 编辑:程序博客网 时间:2024/06/07 00:29

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)) // 报错

更多文章

  • ECMAScript 6 常用特性整理
  • Promise-使用整理
  • Decorator装饰器
  • Iterator、Generator、async、await 异步编程
原创粉丝点击