模拟 iterator 接口:

  1. let obj = {0:1,1:2,2:3,length:3,[Symbol.iterator]:function(){
  2. let index = 0;
  3. let that = this
  4. return {
  5. next(){
  6. return {
  7. value:that[index],
  8. done:index++ >= that.length
  9. }
  10. }
  11. }
  12. }}
  13. let arr = [...obj]
  14. console.log(arr)

使用 generator:

  1. function *gen(val){
  2. let a = yield val + 1
  3. let b = yield a + 2
  4. return a + b
  5. }
  6. function* gen(val) {
  7. let a = yield val + 1
  8. console.log('a', a)
  9. let b = yield a + 2
  10. console.log('b', b)
  11. return a + b
  12. }
  13. let it = gen(3) // 返回的是一个 迭代器 对象
  14. console.log(it.next(4)) // 第一次传参没用的
  15. console.log(it.next(5)) // 这个值会给 a
  16. console.log(it.next(6)) // 这个值会给 b
  17. console.log(it.next(7)) // 已经执行完毕了,传参没意思了
  18. // 输出
  19. // { value: 4, done: false }
  20. // a 5
  21. // { value: 7, done: false }
  22. // b 6
  23. // { value: 11, done: true }
  24. // { value: undefined, done: true }

使用 babel 转义上面的 generator 函数:

  1. "use strict";
  2. var _marked = /*#__PURE__*/regeneratorRuntime.mark(gen);
  3. function gen(val) {
  4. var a, b;
  5. return regeneratorRuntime.wrap(function gen$(_context) {
  6. while (1) {
  7. switch (_context.prev = _context.next) {
  8. case 0:
  9. _context.next = 2;
  10. return val + 1;
  11. case 2:
  12. a = _context.sent;
  13. _context.next = 5;
  14. return a + 2;
  15. case 5:
  16. b = _context.sent;
  17. return _context.abrupt("return", a + b);
  18. case 7:
  19. case "end":
  20. return _context.stop();
  21. }
  22. }
  23. }, _marked, this);
  24. }

看上面编译后的代码发现,第一次执行 gen(),返回的是 regeneratorRuntime.wrap (里面包裹的一个函数),这个函数里面其实就是一个 while 循环,相当于把我们刚开始写的 gen 函数拆成了若干个多部分,每次我们调用 next 方法,指针就往下移动一个位置,并且 return 回去。直到执行完毕。

generator 原理:

  1. function* test() {
  2. let a = 1 + 2;
  3. yield 2;
  4. yield 3;
  5. }

手写 generator 实现:

  1. // cb 也就是编译过的 test 函数
  2. function generator(cb) {
  3. return (function() {
  4. var object = {
  5. next: 0,
  6. stop: function() {}
  7. };
  8. return {
  9. next: function() {
  10. var ret = cb(object);
  11. if (ret === undefined) return { value: undefined, done: true };
  12. return {
  13. value: ret,
  14. done: false
  15. };
  16. }
  17. };
  18. })();
  19. }
  20. // 如果你使用 babel 编译后可以发现 test 函数变成了这样
  21. function test() {
  22. var a;
  23. return generator(function(_context) {
  24. while (1) {
  25. switch ((_context.prev = _context.next)) {
  26. // 可以发现通过 yield 将代码分割成几块
  27. // 每次执行 next 函数就执行一块代码
  28. // 并且表明下次需要执行哪块代码
  29. case 0:
  30. a = 1 + 2;
  31. _context.next = 4;
  32. return 2;
  33. case 4:
  34. _context.next = 6;
  35. return 3;
  36. // 执行完毕
  37. case 6:
  38. case "end":
  39. return _context.stop();
  40. }
  41. }
  42. });
  43. }
  44. let it = test()
  45. it.next()

async await 其实就是 generator 的语法糖,就是带了自动执行器的 generator 函数。使用 babel 编译后,会发现,会先把 async 函数转成 generator 函数(_asyncToGenerator)。

_asyncToGenerator 函数是这样的,其实就是每次调用 next 方法后返回一个 promise ,并且拿到上一个 next 执行后的 value 值:

  1. function _asyncToGenerator(fn) {
  2. return function() {
  3. var gen = fn.apply(this, arguments) // 生成迭代器对象 let it = gen()
  4. return new Promise(function(resolve, reject) {
  5. function step(key, arg) {
  6. try {
  7. var info = gen[key](arg) // 调用迭代器的 next 方法 it.next()
  8. var value = info.value
  9. } catch (error) {
  10. reject(error)
  11. return
  12. }
  13. if (info.done) {
  14. // generator 执行完成后直接调用 async 函数返回的 Promise 的 resolve 方法
  15. resolve(value)
  16. } else {
  17. // 每个 await 后面返回的都是一个 Promise,并且把上一次 it.next 的 value 值给下一个 yield 的返回值,也就是给了 await
  18. return Promise.resolve(value).then(
  19. function(value) {
  20. step('next', value)
  21. },
  22. function(err) {
  23. step('throw', err)
  24. }
  25. )
  26. }
  27. }
  28. return step('next')
  29. })
  30. }
  31. }

generator/iterator 并非异步代码,只是在缺少 async/await 的时候,一些框架(最著名的要数 co)使用这
样的特性来模拟 async/await。

generator+promise 异步:

  1. let { promisify } = require('bluebird')
  2. let fs = require('fs')
  3. let read = promisify(fs.readFile)
  4. function * gen() {
  5. let a = yield read('1.txt', 'utf-8')
  6. let b = yield read('2.txt', 'utf-8')
  7. console.log(a)
  8. console.log(b)
  9. }
  10. let it = gen()
  11. //手动执行:
  12. // it.next().value.then(data => {
  13. // // console.log(data)
  14. // it.next(data).value.then(da => {
  15. // // console.log(da)
  16. // it.next(da)
  17. // })
  18. // })
  19. //模拟co库:
  20. function co(it){
  21. return new Promise((resolve,reject)=>{
  22. function next(data){
  23. let {value,done}= it.next(data)
  24. if(!done){
  25. value.then(re=>{
  26. next(re)
  27. },reject)
  28. }else{
  29. resolve(data)
  30. }
  31. }
  32. next()
  33. })
  34. }
  35. co(it)

generator的自动执行也可用采用thunk函数来处理。只不过yield后面就不能是返回一个promise,而是一个thunk函数。
参考:http://es6.ruanyifeng.com/#docs/generator-async

使用async-await:

  1. async function readAsync(){
  2. let a = await read('1.txt', 'utf-8')
  3. let b = await read('2.txt', 'utf-8')
  4. console.log(a,b)
  5. }
  6. readAsync()

async 函数总是返回一个promise,解决await串行问题,可使用。

  1. Promise.all([read('1.txt', 'utf-8'),read('2.txt', 'utf-8')])

处理异常可用用try catch包裹await。或者在await后面的promise直接处理。

async await继发、并发问题:

async await并发问题:

  1. function sleep(i) {
  2. return new Promise((resolve, reject) => {
  3. setTimeout(function () {
  4. resolve(i);
  5. }, 1000);
  6. });
  7. }
  8. //如果直接这样使用,需要5秒才能全部输出完毕
  9. async function start(finsh_callback) {
  10. console.time('g');
  11. for (let i = 0; i < 5; i++) {
  12. let res = await sleep(i);
  13. console.log(res);
  14. }
  15. console.timeEnd('g');
  16. }
  17. //如果每次都是一个新的async await自执行则是并行的。当前也可以使用Promise.all()
  18. for (let i = 0; i < 5; i++) {
  19. (async () => {
  20. let res = await sleep(i);
  21. console.log(res);
  22. })()
  23. }
  1. function wait(ms) {
  2. return new Promise(r => setTimeout(r, ms))
  3. }
  4. //以下代码执行完毕需要 1000 毫秒,再看看这段代码:
  5. async function series() {
  6. await wait(500)
  7. await wait(500)
  8. return 'done!'
  9. }
  10. //只需 500 毫秒就可执行完毕,因为两个 wait 是同时发生的:
  11. async function parallel() {
  12. const wait1 = wait(500)
  13. const wait2 = wait(500)
  14. await wait1
  15. await wait2
  16. return 'done!'
  17. }

继发执行:

  1. async function dbFuc(db) {
  2. let docs = [{}, {}, {}];
  3. for (let doc of docs) {
  4. await db.post(doc);
  5. }
  6. }

并发执行:

  1. async function dbFuc(db) {
  2. let docs = [{}, {}, {}];
  3. let promises = docs.map((doc) => db.post(doc));
  4. let results = await Promise.all(promises);
  5. console.log(results);
  6. }
  7. // 或者使用下面的写法
  8. async function dbFuc(db) {
  9. let docs = [{}, {}, {}];
  10. let promises = docs.map((doc) => db.post(doc));
  11. let results = [];
  12. for (let promise of promises) {
  13. results.push(await promise);
  14. }
  15. console.log(results);
  16. }

依次远程读取一组 URL,然后按照读取的顺序输出结果:

Promise 的写法如下:

  1. function logInOrder(urls) {
  2. // 远程读取所有URL
  3. const textPromises = urls.map(url => {
  4. return fetch(url).then(response => response.text());
  5. });
  6. // 按次序输出
  7. textPromises.reduce((chain, textPromise) => {
  8. return chain.then(() => textPromise)
  9. .then(text => console.log(text));
  10. }, Promise.resolve());
  11. }

上面代码使用fetch方法,同时远程读取一组 URL。每个fetch操作都返回一个 Promise 对象,放入textPromises数组。然后,reduce方法依次处理每个 Promise 对象,然后使用then,将所有 Promise 对象连起来,因此就可以依次输出结果。

这种写法不太直观,可读性比较差。下面是 async 函数实现。

  1. async function logInOrder(urls) {
  2. for (const url of urls) {
  3. const response = await fetch(url);
  4. console.log(await response.text());
  5. }
  6. }

如果确实需要并发发出远程请求。

  1. async function logInOrder(urls) {
  2. // 并发读取远程URL
  3. const textPromises = urls.map(async url => {
  4. const response = await fetch(url);
  5. return response.text();
  6. });
  7. // 按次序输出
  8. for (const textPromise of textPromises) {
  9. console.log(await textPromise);
  10. }
  11. }

async await 错误处理:

  1. (async function (){
  2. let [err, data] = await fetchData().then(data=>[null,data]).catch(err=>[err,null])
  3. })()