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
複製代碼
相關文章
相關標籤/搜索