ES6系列之Iterator &Generator & Async

Iterator

JavaScript原有的四种表示'集合'的数据结构,Object、Array、Set、Map。shell

遍历器(Iterator)是一种接口,为各类不一样的数据结构提供统一的访问机制。任何数据结构只要部署了Iterator接口,就能够完成遍历操做。编程

Iterator 的做用有三个:promise

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

遍历器原理

遍历器提供了一个指针,指向当前对象的某个属性,使用next方法,就能够将指针移动到下一个属性。next方法返回一个包含value和done两个属性的对象。其中,value属性是当前遍历位置的值,done属性是一个布尔值,表示遍历是否结束。
原生具有 Iterator 接口的数据结构以下:数据结构

  • Array
  • Map
  • Set
  • String
  • TypedArray
  • 函数的 arguments 对象
  • NodeList 对象

对于原生部署 Iterator 接口的数据结构,不用本身写遍历器生成函数,for...of循环会自动遍历它们。其余数据结构(主要是对象)的 Iterator 接口,都须要本身在Symbol.iterator属性上面部署,这样才会被for...of循环遍历。并发

class RangeIterator {
    constructor(start, stop){
        this.value = start;
        this.stop = stop;
    }
    
    [Symbol.iterator]{return this;}
    
    next(){
        let value = this.value;
        if(value < this.stop){
            this.value++;
            return {done : false, value : value};
        }
        return {done : true, value : undefined};
    }
}

function range(start, stop){
    return new RangeIterator(start, stop);
}

for(let v of range(0,3)) {
    console.log(value);  // 0 1 2
}

Generator

Generator 函数是 ES6 提供的一种异步编程解决方案。异步

yield 表达式

因为 Generator 函数返回的遍历器对象,只有调用next方法才会遍历下一个内部状态,因此其实提供了一种能够暂停执行的函数。yield表达式就是暂停标志。async

遍历器对象的next方法的运行逻辑以下。异步编程

(1)遇到yield表达式,就暂停执行后面的操做,并将紧跟在yield后面的那个表达式的值,做为返回的对象的value属性值。函数

(2)下一次调用next方法时,再继续往下执行,直到遇到下一个yield表达式。post

(3)若是没有再遇到新的yield表达式,就一直运行到函数结束,直到return语句为止,并将return语句后面的表达式的值,做为返回的对象的value属性值。

(4)若是该函数没有return语句,则返回的对象的value属性值为undefined。

须要注意的是,yield表达式后面的表达式,只有当调用next方法、内部指针指向该语句时才会执行,所以等于为 JavaScript 提供了手动的“惰性求值”(Lazy Evaluation)的语法功能。

Generator 函数能够不用yield表达式,这时就变成了一个单纯的暂缓执行函数。
yield表达式只能用在 Generator 函数里面,用在其余地方都会报错。

yield表达式若是用在另外一个表达式之中,必须放在圆括号里面。

function* demo() {
  console.log('Hello' + yield); // SyntaxError
  console.log('Hello' + yield 123); // SyntaxError

  console.log('Hello' + (yield)); // OK
  console.log('Hello' + (yield 123)); // OK
}

yield表达式用做函数参数或放在赋值表达式的右边,能够不加括号。

function* demo() {
  foo(yield 'a', yield 'b'); // OK
  let input = yield; // OK
}

next 方法的参数

yield表达式自己没有返回值,或者说老是返回undefined。next方法能够带一个参数,该参数就会被看成上一个yield表达式的返回值。

与 Iterator 接口的关系

因为 Generator 函数就是遍历器生成函数,所以能够把 Generator 赋值给对象的Symbol.iterator属性,从而使得该对象具备 Iterator 接口。

var myIterable = {};
myIterable[Symbol.iterator] = function* () {
  yield 1;
  yield 2;
  yield 3;
};

[...myIterable] // [1, 2, 3]

实例方法

Generator.prototype.return()

Generator 函数返回的遍历器对象,还有一个return方法,能够返回给定的值,而且终结遍历 Generator 函数。

Generator.prototype.throw()

Generator 函数返回的遍历器对象,都有一个throw方法,能够在函数体外抛出错误,而后在 Generator 函数体内捕获。

yield* 表达式

若是在 Generator 函数内部,调用另外一个 Generator 函数,默认状况下是没有效果的。
这个就须要用到yield*表达式,用来在一个 Generator 函数里面执行另外一个 Generator 函数。

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

// 等同于
function* bar() {
  yield 'x';
  yield 'a';
  yield 'b';
  yield 'y';
}

// 等同于
function* bar() {
  yield 'x';
  for (let v of foo()) {
    yield v;
  }
  yield 'y';
}

for (let v of bar()){
  console.log(v);
}
// "x"
// "a"
// "b"
// "y"

做为对象属性的 Generator 函数

若是一个对象的属性是 Generator 函数,能够简写成下面的形式。

let obj = {

  • myGeneratorMethod() {
    ···

}
};

Generator 函数的this

Generator 函数老是返回一个遍历器,ES6 规定这个遍历器是 Generator 函数的实例,也继承了 Generator 函数的prototype对象上的方法。

function* g() {}

g.prototype.hello = function () {
  return 'hi!';
};

let obj = g();

obj instanceof g // true
obj.hello() // 'hi!'

上面代码代表,Generator 函数g返回的遍历器obj,是g的实例,并且继承了g.prototype。可是,若是把g看成普通的构造函数,并不会生效,由于g返回的老是遍历器对象,而不是this对象。

function* g() {
  this.a = 11;
}

let obj = g();
obj.next();
obj.a // undefined

上面代码中,Generator 函数g在this对象上面添加了一个属性a,可是obj对象拿不到这个属性。

Async

async 函数是 Generator 函数的语法糖。
async函数就是将 Generator 函数的星号(*)替换成async,将yield替换成await,仅此而已。

const fs = require('fs');

const readFile = function (fileName) {
  return new Promise(function (resolve, reject) {
    fs.readFile(fileName, function(error, data) {
      if (error) return reject(error);
      resolve(data);
    });
  });
};

const gen = function* () {
  const f1 = yield readFile('/etc/fstab');
  const f2 = yield readFile('/etc/shells');
  console.log(f1.toString());
  console.log(f2.toString());
};

写成async函数,就是下面这样。

const asyncReadFile = async function () {
  const f1 = await readFile('/etc/fstab');
  const f2 = await readFile('/etc/shells');
  console.log(f1.toString());
  console.log(f2.toString());
};

async函数对 Generator 函数的改进,体如今如下四点。

  1. 内置执行器。
    Generator 函数的执行必须靠执行器,因此才有了co模块,而async函数自带执行器。也就是说,async函数的执行,与普通函数如出一辙,只要一行。
    asyncReadFile();
  2. 更好的语义。
    async和await,比起星号和yield,语义更清楚了。async表示函数里有异步操做,await表示紧跟在后面的表达式须要等待结果。
  3. 更广的适用性。
    co模块约定,yield命令后面只能是 Thunk 函数或 Promise 对象,而async函数的await命令后面,能够是 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时等同于同步操做)。
  4. 返回值是 Promise。
    async函数的返回值是 Promise 对象,这比 Generator 函数的返回值是 Iterator 对象方便多了。你能够用then方法指定下一步的操做。

await 命令

正常状况下,await命令后面是一个 Promise 对象。若是不是,就返回对应的值。
一、await命令后面的Promise对象,运行结果多是rejected,因此最好把await命令放在try...catch代码块中。

async function myFunction() {
  try {
    await somethingThatReturnsAPromise();
  } catch (err) {
    console.log(err);
  }
}

// 另外一种写法
async function myFunction() {
  await somethingThatReturnsAPromise()
  .catch(function (err) {
    console.log(err);
  });
}

二、 多个await命令后面的异步操做,若是不存在继发关系,最好让它们同时触发。

// 写法一
let [foo, bar] = await Promise.all([getFoo(), getBar()]);

// 写法二
let fooPromise = getFoo();
let barPromise = getBar();
let foo = await fooPromise;
let bar = await barPromise;

上面两种写法,getFoo和getBar都是同时触发,这样就会缩短程序的执行时间。
三、await命令只能用在async函数之中,若是用在普通函数,就会报错。

async function dbFuc(db) {
  let docs = [{}, {}, {}];

  // 报错
  docs.forEach(function (doc) {
    await db.post(doc);
  });
}

四、若是确实但愿多个请求并发执行,可使用Promise.all方法。当三个请求都会resolved时,下面两种写法效果相同。

async function dbFuc(db) {
  let docs = [{}, {}, {}];
  let promises = docs.map((doc) => db.post(doc));

  let results = await Promise.all(promises);
  console.log(results);
}

// 或者使用下面的写法
async function dbFuc(db) {
  let docs = [{}, {}, {}];
  let promises = docs.map((doc) => db.post(doc));

  let results = [];
  for (let promise of promises) {
    results.push(await promise);
  }
  console.log(results);
}

注意事项

async:

  • async函数返回promise对象
  • 没有返回值 ,返回Promise.resolve(undefined)
  • 一旦遇到await命令就会先返回,等到异步操做完成,再接着执行后面的语句
  • 任何一个await语句的promise对象变成reject状态,async会中断执行

await:

  • await只能在async内使用
  • 异常处理,try catch
相关文章
相关标签/搜索