JS 中的 Iterator, Generator, async

Iterator

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

Iterator 的遍历过程:java

  1. 建立一个指针对象,指向当前数据结构的起始位置。
  2. 不断调用指针对象的next方法

每一次调用next方法,都会返回数据结构的当前成员的信息。(返回一个包含value和done两个属性的对象。其中,value属性是当前成员的值,done属性是一个布尔值,表示遍历是否结束。)编程

ES6 规定,一个数据结构只要具备Symbol.iterator属性,就能够认为是“可遍历的”。Symbol.iterator属性自己是一个函数,就是当前数据结构默认的遍历器生成函数。(Symbol数组

Symbol.iterator,它是一个表达式,返回Symbol对象的iterator属性,这是一个预约义好的、类型为Symbol的特殊值。promise

const obj = {
  [Symbol.iterator] : function () {
    return {
      next: function () {
        return {
          value: 1,
          done: true
        };
      }
    };
  }
}

class LinkedList {
    [Symbol.iterator] () {
        return {
            next () {
                return { value: 1, done: true }
            }
        }
    }
}
复制代码

原生数据结构部署了遍历器接口有:Array Map Set String TypedArray 函数的 arguments 对象 NodeList 对象数据结构

for...of

只要部署了遍历器接口就可使用for...of遍历。异步

class RangeIterator {
  constructor(start, stop) {
    this.value = start;
    this.stop = stop;
  }

  [Symbol.iterator]() { return this; }

  next() {
    var 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 (var value of range(0, 3)) {
  console.log(value); // 0, 1, 2
}

let iterable = {
  0: 'a',
  1: 'b',
  2: 'c',
  length: 3,
  [Symbol.iterator]: Array.prototype[Symbol.iterator]
};
for (let item of iterable) {
  console.log(item); // 'a', 'b', 'c'
}
复制代码

如下场景会调用 Iterator 接口async

let [first, ...rest] = [1,2,3] // 解构赋值

[...'hi'] // 扩展运算符

let generator = function* () {
  yield* [1,2,3] // yield*
};

// for...of
// Array.from()
// Map(), Set(), WeakMap(), WeakSet()(好比new Map([['a',1],['b',2]]))
// Promise.all() Promise.race()
复制代码

遍历器返回对象除了next方法还有returnthrow方法,它们是可选的。异步编程

return方法的使用场合是,若是for...of循环提早退出(一般是由于出错,或者有break语句),就会调用return方法。return方法必须返回一个对象。函数

Generator

Generator 函数是 ES6 提供的一种异步编程解决方案,执行 Generator 函数会返回一个遍历器对象。

Generator 函数使用function*定义(有没有空格都行),内部可使用yieldyield*表达式。

function* g() {
    yield 1
    yield 2
    return 3
    yield 4
}

let a = g()
// 调用 Generator 函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象,遍历器对象

console.log(a)

/* __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 [[GeneratorLocation]]: VM89:1 [[GeneratorStatus]]: "suspended" [[GeneratorFunction]]: ƒ* g() [[GeneratorReceiver]]: Window [[Scopes]]: Scopes[2] */

// 必须调用遍历器对象的next方法,使得指针移向下一个状态。也就是说,每次调用next方法,内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一个yield表达式(或return语句)为止。

a.next() // { value: 1, done: false }
a.next() // { value: 2, done: false }
a.next() // { value: 3, done: true }
a.next() // { value: undefined, done: true }
复制代码

Generator 函数是分段执行的,yield表达式是暂停执行的标记,而next方法能够恢复执行,当碰到yield就返回yield后面的值和donefalse,若是遇到return就返回return后的值和donetrue的对象,若是没有碰到yieldreturn则返回值为undefineddonetrue的对象。

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

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
}

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

任意一个对象的Symbol.iterator方法,等于该对象的遍历器生成函数,调用该函数会返回该对象的一个遍历器对象,能够把 Generator 赋值给对象的Symbol.iterator属性,从而使得该对象具备 Iterator 接口。

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

[...myIterable] // [1, 2, 3]
复制代码

yield能够有返回值,它经过next方法传入。

function* g() {
    let v1 = yield
    console.log(v1)
    let v2 = yield 2
    console.log(v2)
}

let a = g()
a.next() // { value: undefined, done: false }
a.next([1,2,3]) // { value: 2, done: false }
a.next() // { value: undefined, done: true }

// [1,2,3]
// undefined
复制代码

上面的例子,第一个next方法执行到第一个yield暂停,执行第二次next方法时,咱们用[1,2,3]作为参数,这时第一个yield就返回[1,2,3],因此第一次打印[1,2,3]第二次打印undefined

因此对第一个next方法传递参数是没有用的,第二个next的参数才做为第一个yield的返回值。

for...of循环能够自动遍历 Generator函数运行时生成的Iterator对象,且此时再也不须要调用next方法。

function* foo() {
  yield 1;
  yield 2;
  yield 3;
  yield 4;
  yield 5;
  return 6;
}

for (let v of foo()) {
  console.log(v);
}
// 1 2 3 4 5
复制代码

只要donetruefor...of就会终止循环,因此return的返回值没有打印。

Generator 函数原型上有throw方法,能够用来在函数体外抛出错误,而后在 Generator 函数体内捕获。

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

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

try {
  i.throw('a'); // throw 方法能够接受一个参数,该参数会被catch语句接收
  i.throw('b'); // throw 方法会附带执行一次 next 方法
} catch (e) {
  console.log('外部捕获', e);
}
// 内部捕获 a
// 外部捕获 b
复制代码

若是内部没有捕获,那么错误就会跑到外面来,被外部捕获。若是内外都没有捕获错误,那么程序将报错,直接中断执行。

在使用throw以前,必须执行一次next方法,不然抛出的错误不会被内部捕获,而是直接在外部抛出,致使程序出错。

函数体内的错误能够被外部捕获。

function* g() {
    yield 1
    yield 2
    throw new Error('err')
    yield 3
}

let a = g()

try {
    console.log(a.next()) // { value: 1, done: tfalse }
    console.log(a.next()) // { value: 2, done: false }
    console.log(a.next()) // 报错
} catch(e) {}

console.log(a.next()) // { value: undefined, done: true }
复制代码

只要内部错误没有捕获,跑到外面来,那么迭代器就会自动中止。

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

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

var g = gen();

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

// ---------------
function* numbers () {
  yield 1;
  try {
    yield 2;
    yield 3;
  } finally {
    yield 4;
    yield 5;
  }
  yield 6;
}
var g = numbers();
g.next() // { value: 1, done: false }
g.next() // { value: 2, done: false }
g.return(7) // { value: 4, done: false }
g.next() // { value: 5, done: false }
g.next() // { value: 7, done: true }

// 若是 Generator 函数内部有try...finally代码块,且正在执行try代码块,那么return方法会推迟到finally代码块执行完再执行
复制代码

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"

function* g() {
    yield 1
    yield 2
    return 3
}

function* e() {
    let returnValue = yield* g()
    console.log(returnValue) // 3
    // 若是另外一个函数带有 return 则须要本身获取
    
    yield* ['a', 'b', 'c'] // 还能够是 数组,字符串这些原生带有迭代器的对象
}
复制代码

若是yield表达式后面跟的是一个遍历器对象,须要在yield表达式后面加上星号,代表它返回的是一个遍历器对象。

function* iterTree(tree) {
  if (Array.isArray(tree)) {
    for(let i=0; i < tree.length; i++) {
      yield* iterTree(tree[i]);
    }
  } else {
    yield tree;
  }
}

const tree = [ 'a', ['b', 'c'], ['d', 'e'] ];
[...iterTree(tree)]
复制代码

yield* 能够轻松的抹平数组。

Generator 函数不能和new命令一块儿用。

Async

ES2017 标准引入了 async 函数,使得异步操做变得更加方便。它能够很是清晰的将异步操做写成同步操做。

let p = async function getData() {
    let data = await db.getData()
    console.log(data)
}

console.log(p)
/* Promise {<resolved>: undefined} __proto__: Promise [[PromiseStatus]]: "resolved" [[PromiseValue]]: undefined */

let b = async function () {} // 函数表达式
let c = async () => {} // 箭头函数
复制代码

async 函数 以async开头,其内部可使用await等待异步操做完成。async函数会返回一个Promise对象,因此可使用then方法添加回调函数。

函数执行的时候,一旦遇到await就会先返回,等到异步操做完成,再接着执行函数体内后面的语句。

async函数内部return语句返回的值,会成为then方法回调函数的参数,async函数内部抛出错误,会致使返回的 Promise 对象变为reject状态。抛出的错误对象会被catch方法回调函数接收到。

async function a() {
    throw new Error('err')
}

a() // Uncaught (in promise) Error 报错,但不会终止程序

console.log(123) // 正常执行
复制代码

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

  1. await命令后面通常是一个 Promise 对象,返回该对象的结果。若是不是 Promise 对象,就直接返回对应的值。
  2. await命令后面是一个thenable对象(即定义then方法的对象),那么await会将其等同于 Promise 对象。
  3. 任何一个await语句后面的 Promise 对象变为reject状态,那么整个async函数都会中断执行。
async function main() {
  try {
    const val1 = await firstStep();
    const val2 = await secondStep(val1);
    const val3 = await thirdStep(val1, val2);

    console.log('Final: ', val3);
  }
  catch (err) {
    console.error(err);
  }
} // 咱们能够将多个 await 命令放入 try-catch 中
复制代码

async函数其实它就是 Generator 函数的语法糖。

async function fn(args) {
  // ...
}

// 等同于

function fn(args) {
  return spawn(function* () {
    // ...
  });
}

function spawn(genF) {
  return new Promise(function(resolve, reject) { // 返回一个 Promise 对象
    const gen = genF();
    function step(nextF) {
      let next;
      try {
        next = nextF();
      } catch(e) {
        return reject(e); // 执行 fn 中的代码,若是出错直接 reject 返回的 Promise
      }
      if(next.done) { // 若是 fn 执行完成则 resolve fn return 的值
        return resolve(next.value);
      }
      Promise.resolve(next.value).then(function(v) { 
        // 将 yield 返回的值变成 Promise 并执行它的 then 方法
        step(function() { return gen.next(v); });
        // 当 yield 后面的 Promise 执行成功完成时则继续执行 fn 函数
        // 并将它产生的值传入 fn 函数
      }, function(e) {
        step(function() { return gen.throw(e); });
        // 若是出现错误则将错误传入 fn 内部。
        // 若是内部没有捕获则被本函数上面的 try-catch 捕获
      });
    }
    step(function() { return gen.next(undefined); });
  });
}
复制代码

异步遍历器

异步遍历器和遍历器的区别在于,异步遍历器返回的是一个 Promise 对象,可是它的值的格式和遍历器同样。异步遍历器接口,部署在Symbol.asyncIterator属性上面。

异步遍历器它的next不用等到上一个 Promise resolve 了才能调用。这种状况下,next方法会累积起来,自动按照每一步的顺序运行下去。

const asyncIterable = createAsyncIterable(['a', 'b']);
const asyncIterator = asyncIterable[Symbol.asyncIterator]();
const [{value: v1}, {value: v2}] = await Promise.all([
  asyncIterator.next(), asyncIterator.next()
]);

console.log(v1, v2); // a b
复制代码

for await...of

for await...of循环,是用于遍历异步的 Iterator 接口。

async function f() {
  for await (const x of createAsyncIterable(['a', 'b'])) {
    console.log(x);
  }
}
// a
// b
复制代码

若是next方法返回的 Promise 对象被rejectfor await...of就会报错,要用try...catch捕捉。

它也能够用于同步遍历器。

异步 Generator 函数

就像 Generator 函数返回一个同步遍历器对象同样,异步 Generator 函数的做用,是返回一个异步遍历器对象。

async function* gen() {
  yield 'hello';
}
const genObj = gen();
genObj.next().then(x => console.log(x));
// { value: 'hello', done: false }

// 同步 Generator 函数
function* map(iterable, func) {
  const iter = iterable[Symbol.iterator]();
  while (true) {
    const {value, done} = iter.next();
    if (done) break;
    yield func(value);
  }
}

// 异步 Generator 函数
async function* map(iterable, func) {
  const iter = iterable[Symbol.asyncIterator]();
  while (true) {
    const {value, done} = await iter.next();
    if (done) break;
    yield func(value);
  }
}
复制代码

Generator 函数处理同步操做和异步操做时,可以使用同一套接口。异步 Generator 函数内部,可以同时使用awaityield命令。

若是异步 Generator 函数抛出错误,会致使 Promise 对象的状态变为reject,而后抛出的错误被catch方法捕获。

yield*

yield*语句也能够跟一个异步遍历器。

async function* gen1() {
  yield 'a';
  yield 'b';
  return 2;
}

async function* gen2() {
  // result 最终会等于 2
  const result = yield* gen1();
}

for await (const x of gen2()) {
  console.log(x);
}
// a
// b
复制代码
相关文章
相关标签/搜索