导航


HTML

CSS

JavaScript

浏览器 & 网络

版本管理

框架

构建工具

TypeScript

性能优化

软实力

算法

UI、组件库

Node

冷门技能

迭代器 -> 生成器

ES6 写法

写一个 generator 生成器 -> 调用后生成一个 迭代器 iterator

function * test(arr) {
	for (const value of arr) {
  	yield value;
  }
}

const iterator = test([1,2,3]);
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());

Untitled

ES5 写法

function generator(arr) {
	let nextIdx = 0,
      len = arr.length;
  
  return {
  	next() {
    	return nextIdx < len
      	? { value: arr[nextIdx++], done: false }
      	: { value: undefined, done: true }
    }
  }
}

const iterator = generator([1,2,3]);
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());

⭐️ 中间件为啥最后每次要执行 next()

Untitled

为的是能够进一步执行后边中间件的逻辑,一旦当前中间件走不通,就不调用 next,从而阻断继续执行后续代码

;((functions) => {
  function * generator(arr) {
    for (let i = 0; i < arr.length; i++) {
      yield arr[i];
    }
  }

  const iterator = generator(functions);

  const init = () => {
    nextDo(iterator.next());
  }

  function nextDo(n) {
    n.value(function() { // 第一次迭代时,test1接收的参数next就是此处的匿名函数
      const n = iterator.next();
      if (!n.done) {
        nextDo(n);
      } else {
        return;
      }
    });
  }

  init();
})([
  function test1(next) {
    console.log('test1');
    console.log(next); //这里的next就是上边 n.value 中的匿名函数
    next();
  },
  function test2(next) {
    console.log('test2');
    next();
  },
  function test3(next) {
    console.log('test3');
    next();
  },
  function test4(next) {
    console.log('test4');
    next();
  },
  function test5(next) {
    console.log('test5');
    next();
  },
]);

Untitled

随时暂停中间件执行 - 类似于安检

;((functions) => {
  function * generator(arr) {
    for (let i = 0; i < arr.length; i++) {
      const func = arr[i];
      yield func;
    }
  }

  const iterator = generator(functions);

  const init = () => {
    nextDo(iterator.next());
  }

  function nextDo(n) {
    n.value(function() { // 第一次迭代时,test1接收的参数next就是此处的匿名函数
      const n = iterator.next();
      if (!n.done) {
        nextDo(n);
      } else {
        return;
      }
    });
  }

  init();
})([
  function test1(next) {
    console.log('test1');
    let username = '233'
    if (username.length > 6) {
      next();
    }
  },
  function test2(next) {
    console.log('test2');
    next();
  },
  function test3(next) {
    console.log('test3');
    next();
  },
  function test4(next) {
    console.log('test4');
    next();
  },
  function test5(next) {
    console.log('test5');
    next();
  },
]);

Untitled

demo

Untitled

应用

日志打印

;(function(doc) {
  function Log() {
    this.oInput = doc.getElementsByTagName("input")[0];
    this.oBtn = doc.getElementsByTagName("button")[0];
    this.oList = doc.getElementsByClassName("logs")[0];
    this.logs = [];
    this.iterator = generator(this.logs);
    this.init();
  }

  Log.prototype.init = function() {
    this.bindEvent();
  }

  Log.prototype.bindEvent = function() {
    this.oBtn.addEventListener('click', handleBtnClick.bind(this), false);
  }

  function handleBtnClick(e) {
    const val = this.oInput.value;
    this.logs.push({
      date: new Date(),
      value: val,
    })

    _addLog.call(this, this.iterator.next().value);
  }

  function _addLog(log) {
    const oLi = doc.createElement("li");
    oLi.innerHTML = `
      操作内容: ${log.value}
      操作时间: ${log.date}
    `;

    this.oList.appendChild(oLi);
  }

  function * generator(arr) {
    for (const value of arr) {
      yield value;
    }
  }

  window.Log = Log;
})(document);
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>日志打印</title>
</head>
<body>
  <div class="container">
    <input type="text" placeholder="输入操作">
    <button>执行</button>
    <ul class="logs"></ul>
  </div>
  <script src="./index.js"></script>
  <script>
    new Log();
  </script>
</body>
</html>

Untitled

generator + co 实现 async + await

generator + co异步迭代函数 === async函数 + await

generator生成器函数 + yield + co === async + await => 语法糖

generator 实现根据学生 id 找课程

generator2.js

const fs = require('fs').promises; // promise化fs下所有异步方法

function * getUserClasses(uid) {
                  // 返回一个promise,看到yield就停止了,下次next才会把这次yield产出值赋值给userDatas
  let userDatas = yield fs.readFile('./data/user.json', 'utf-8');
  userDatas = JSON.parse(userDatas);
  const userData = userDatas.find(user => user.id === uid);
  let classDatas = yield fs.readFile('./data/class.json', 'utf-8');
  classDatas = JSON.parse(classDatas);

  let userClassData = {
    id: userData.id,
    name: userData.name,
    classes: []
  };

  classDatas.map(c => {
    const studentsArr = JSON.parse(c.students);
    studentsArr.map(s => {
      if (s === uid) {
        userClassData.classes.push({
          id: c.id,
          name: c.name
        });
      }
    });
  });

  return userClassData;
}

module.exports = {
  getUserClasses
}

// 最终期望的结果
// [
//   {
//     "id": 1,
//     "name": "蕾姆",
//     classes: [
//       {
//         "id": 1,
//         "name": "前端"
//       },
//       {
//         "id": 2,
//         "name": "后端"
//       }
//     ]
//   }
// ]

index2.js

const { getUserClasses } = require('./generator2');

const uid = 1;
const it = getUserClasses(uid);
const { value, done } = it.next();
value.then(res => {
  const { value, done } = it.next(res);
  value.then(res => {
    const { value, done } = it.next(res);
    console.log(value)
  });
});

// {
//   id: 1,
//   name: '雷姆',
//   classes: [ { id: '1', name: '前端' }, { id: '2', name: '后端' } ]
// }

太繁琐,又是个回调地狱。

期望写法

getUserClasses(uid).then(res => {
	console.log(res); 
})
.catch(err => {
	console.log(err);
});

编写 co 迭代器递归函数

generator2.js

...
function co(iterator) { // 异步迭代函数
  // co最终要.then拿到最终值,所以要return一个promise实例
  return new Promise((resolve, reject) => {
    // 迭代器递归函数 参数:传给next的数据
    function walk(data) {
      // 执行 next => value done 对象
      const { value, done } = iterator.next(data);
      if (!done) {
        // value 有可能不是个 Promise,为了能往下走,再用 Promise.resolve 包一下
        // value -> then -> 拿到新的迭代时 程序执行的结果
        Promise.resolve(value).then(res => {
          // 肯定要继续执行迭代器递归函数
          walk(res);
          // promise出错了 -> 本次返回的Promise的reject
        }, reject);
      } else {
        // done === true,迭代结束,成功抛出value
        resolve(value);
      }
    }
    walk();
  });
}
...

index2.js

const { getUserClasses, co } = require('./generator2');

const uid = 1;

co(getUserClasses(uid)).then(res => {
  console.log(res);
});

// {
//   id: 1,
//   name: '雷姆',
//   classes: [ { id: '1', name: '前端' }, { id: '2', name: '后端' } ]
// }

async / await

  1. 把之前 yield 改为 await ,把 * 改为 async
  2. 把 getUserClasses 改名为 asyncGetUserClasses
  3. 把上边的 co(getUserClasses(uid)) 改为 asyncGetUserClasses(uid)

generator2.js

async function asyncGetUserClasses(uid) {
                  // 返回一个promise,看到yield就停止了,下次next才会把这次yield产出值赋值给userDatas
  let userDatas = await fs.readFile('./data/user.json', 'utf-8');
  userDatas = JSON.parse(userDatas);
  const userData = userDatas.find(user => user.id === uid);
  let classDatas = await fs.readFile('./data/class.json', 'utf-8');
  classDatas = JSON.parse(classDatas);

  let userClassData = {
    id: userData.id,
    name: userData.name,
    classes: []
  };

  classDatas.map(c => {
    const studentsArr = JSON.parse(c.students);
    studentsArr.map(s => {
      if (s === uid) {
        userClassData.classes.push({
          id: c.id,
          name: c.name
        });
      }
    });
  });

  return userClassData;
}

index2.js

const { getUserClasses, co, asyncGetUserClasses } = require('./generator2');

const uid = 1;

asyncGetUserClasses(uid).then(res => {
  console.log(res);
});

// {
//   id: 1,
//   name: '雷姆',
//   classes: [ { id: '1', name: '前端' }, { id: '2', name: '后端' } ]
// }

结论

generator + co异步迭代函数 === async函数 + await

generator生成器函数 + yield + co === async + await => 语法糖

传递 generator 参数的 Co 函数

function generatorToAsync(generatorFn) {
  return function() {
    const gen = generatorFn.apply(this, arguments) // gen有可能传参

    // 返回一个Promise
    return new Promise((resolve, reject) => {

      function go(key, arg) {
        let res
        try {
          res = gen[key](arg) // 这里有可能会执行返回reject状态的Promise
        } catch (error) {
          return reject(error) // 报错的话会走catch,直接reject
        }

        // 解构获得value和done
        const { value, done } = res
        if (done) {
          // 如果done为true,说明走完了,进行resolve(value)
          return resolve(value)
        } else {
          // 如果done为false,说明没走完,还得继续走

          // value有可能是:常量,Promise,Promise有可能是成功或者失败
          return Promise.resolve(value).then(val => go('next', val), err => go('throw', err))
        }
      }

      go("next") // 第一次执行
    })
  }
}

使用:

function* gen() {
  const num1 = yield fn(1)
  console.log(num1) // 2
  const num2 = yield fn(num1)
  console.log(num2) // 4
  const num3 = yield fn(num2)
  console.log(num3) // 8
  return num3
}

const genToAsync = generatorToAsync(gen)
const asyncRes = genToAsync()
console.log(asyncRes) // Promise
asyncRes.then(res => console.log(res)) // 8