快速掌握ES6 iterator Generator和async 之间的关系及其用法

快速掌握ES6 iterator Generator和async 之间的关系及其用法

1.遍历器iterator

1.1 for遍历

首先从远古讲起,刚出js的时候如何遍历一个数组呢?

 var arr = [1, 2, 3, 4, 7, 8, 9]
    for (let i = 0;i < arr.length;i++) {
      console.log(arr[i]);
    }

1.2 forEach遍历

看起来笨的一批,所以ES5给你研究了一个foreach方法,但是这个方法不能break,也不能改变数组自身的值,也不能返回任何值。

      var arr = [1, 2, 3, 4, 7, 8, 9]
    var arr2 = arr.forEach((element, index) => {
      console.log('第' + index + '个数值为:' + element);
      return element * 2;
    });
    console.log(arr2) // undefined
        console.log(arr1) // [1, 2, 3, 4, 7, 8, 9]

所以说foreach只给你最基本的操作,其他一概不管,如果你想要改变自身的值或者有break和countinue操作我们可以使用map操作,不展开讲了,之前专门写了篇博客总结了下。

wzr:数组遍历方法总结

1.3 for-in遍历

那么ES6专门为遍历数组提供了一种方法,就是for-of。说道for-of,不得不提到了for-in

那么关于他们俩的区别,我也专门写了一篇博客,不在这展开讲了。

wzr:深入理解枚举属性与for-in和for-of

值得一提的是for-in是可以遍历数组的,但是不推荐用for-in去遍历数组,为什么呢?因为for -in 返回的可枚举属性是字符类型,不是数字类型,如果"0","1"这样的属性和1,2数组发生相加,很可能不是直接相加,而是字符串的叠加。例如

const items = [1,2,3,4]

for(item in items){
  let tempitem = item+1 
  console.log(items[tempitem]) // undefined
  console.log(tempitem) // 01 21 32 41 item与数字相加 会得到字符串相加的结果,所以导致上面找不着items相应的项
}

所以为了避免歧义,还是不要用for-in遍历数组的好。

1.4 for-of

那么接下来就进入正题了,因为for-in在数组这边比较难用,所以ES6新添加的for-of来弥补for-in的不足。这是个正儿八经遍历数组的方法。与 forEach() 不同的是,它支持 break、continue 和 return 语句。而且他本身的语法非常的简单:

for (variable of iterable) {
    //statements
}
  • variable: 在每次迭代中,将不同属性的值分配给变量。

  • iterable: 被迭代枚举其属性的对象。

而且关键的问题是for-of不仅可以遍历数组,他可以遍历很多类数组对象。

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

而他的原理在于这些类数组对象中都有一个属性,就是Symbol.iterator,也就是说,只要带Symbol.iterator他都能遍历,我们单独把这个属性拿出来,自己手动执行next()方法就会看到我们成功的遍历了这个数组:

const items = [1,2,3,4]

const giao =  items[Symbol.iterator]()

console.log(giao.next()) // {value: 1, done: false}
console.log(giao.next()) // {value: 2, done: false}
console.log(giao.next()) // {value: 3, done: false}
console.log(giao.next()) // {value: 4, done: false}
console.log(giao.next()) // {value: undefined, done: true}

同理,我们可以已通过手动写一个iterator来更深入的了解他的原理:

Array.prototype.myiterator = function(){
  let i = 0 
  let items = this
  return {
    next(){
      const done  = i >= items.length
      const value = done ? undefined : items[i++]
      return {
        value, done
      }
    }
  }
}

const item = [1,2,3,4]

// 控制台
> const giao = item.myiterator() //当我们获得到遍历器时,我们只需要代替for-of执行myiterator即可遍历这个数组。
> giao.next()
{value: 1, done: false}
> giao.next()
{value: 2, done: false}
> giao.next()
{value: 3, done: false}
> giao.next()
{value: 4, done: false}
> giao.next()
{value: undefined, done: true}

效果跟for of是一样的。另外值得注意的是,你可以在任意对象里添加这个属性,让他们可遍历。

const items = [ 'blue', 'yellow', 'white', 'black']

for(item of items){
  console.log(item)
}

1.5总结

遍历器如果存在与一个对象内,他就可以让这个对象可供for-of遍历,for-of的遍历方法就是不停地调用遍历器的next()方法,直到done属性变为true

2.生成器 Generator

为什么拿生成器和遍历器一起讲呢,因为本质上,生成器器函数返回的就是一个遍历器!

生成器的语法很简单,就是在function后面加个*,然后用yield来返回对应的值。(其实也可以将yield可以看做return,只不过需要next()来进行外调用,还有一个函数只能由一个return ,而yield可以有多个)

function* items(){
    yield "1"
    yield "2"
    yield "3"
}

const num = items()

// 控制台
> num.next()
{value: "1", done: false}
> num.next()
{value: "2", done: false}
> num.next()
{value: "3", done: false}
> num.next()
{value: undefined, done: true}
> num.next()
{value: undefined, done: true}

那么我们yield的之前同样也可以加入运算

function* items(){
    let i =0
    yield i // 0
    i++
    yield i // 1
    i++
    yield i // 2
    i++ //这个就不运行了,因为他在yield之后
}

const num = items()

//不用浏览器控制台,直接打印也行
console.log(num.next()) // {value:0,done:false}
console.log(num.next()) // {value:1,done:false}
console.log(num.next()) // {value:2,done:false}
console.log(num.next()) // {value:undefined,done:true}

利用这样的特性,我们可以用Generator来进行ajax的等待操作。

function ajax(url){
  // 请求成功自动调用next()方法。然后返回数据结果
  axios.get(url).then(res => gen.next(res.data))
}
function* step(){
  const class = yield ajax.get(`http://laotie.com/getclass`)
  const score = yield ajax.get(`http://laotie.com/getscore?name=${class[0].name})
}

// 获得这个函数的"遍历器"
const gen = step()
// 启动"遍历器",不启动就不会动
gen.next() // 获取class
gen.next() // 获取到score

因为第二个get请求依赖第一个请求的结果,所以我们解决办法第一个是运用Promise的回调来限制他们的先后顺序,但是在我们学习了生成器之后我们发现生成器的特性很适合做这样的事。也就是只有当第一个请求执行完之后,才能顺序执行第二个请求。

另外还有一些小的特性

*可以添加到任意位置,都不会影响生成genterator。下面的写法都是可以的。

function * foo(x, y) { ··· }
function *foo(x, y) { ··· }
function* foo(x, y) { ··· }
function*foo(x, y) { ··· }

关于Generator的Thunk或者co模块,因为ES8的async的加入,极大地简化了Genrtator的操作,针对原理和实战操作也就没有那么多要求了,接下来主要说一说async函数。

3.async

1.语法

准确的说,async就是Generator的语法糖,首先看他的语法。

async function laotie(){
  const data = await dosomething()
}

可以看到,

  • 原来的*async代替
  • 原来的yieldawait代替

这样做的直接的好处就是显得更加语义化,可读性更强。但是其实async做到的远不止如此。

  1. 首先第一点,就是async不用不断执行next()了,async函数内置了执行器,使得我们在调用函数时,只需要直接调用即可。如下:

    // 接上一个代码块
     laotie()
  2. 现在async 的await还是保留着等待的功能,但是因为没有了next(),所以在调用await不会像yield那样返回值了。在async中,只有return返回,而且返回的是一个promise对象。

    拿上面的代码直接改写成async加await的格式,

    async function items(){
     let i =0
     await i 
     i++
     await i 
     i++
     await i 
     i++ 
    }
    
    console.log(items()) // Promise {<pending>}

    直接调用方法我们能看到返回的是一个状态为resolved的Promise对象,而不是Iterator。

    而这个对象,返回的值就是函数里return出来的值。我们可以用then()函数来接受这个值并打印他。

    async function items(){
     let i =3
     return i
    }
    
    items().then(res=>{
     console.log(res) // 3
    })

    当然这么举例子准定不是正经的用法,这些例子主要用于区分Generator和async函数之前的区别。

2.用法

正确的用法现在是在await之后加入一个异步函数,await相当于将这个异步函数转化为同步函数,等这个异步函数执行完毕返回resolved的时候时候才往下执行进一步的操作。例如:

async function asyncPrint(value, ms) {
  await new Promise(resolve => {
    setTimeout(resolve, ms);
  });
  console.log(value);
}

asyncPrint('hello world', 1000); // 一秒后打印hellow world

如果这个async 的函数中间有多个await,那么就让多个await以排队的方式执行。

用法2:

先让我们把之前generator的例子拿过来

function ajax(url){
  // 请求成功自动调用next()方法。然后返回数据结果
  axios.get(url).then(res => gen.next(res.data))
}
function* step(){
  const class = yield ajax.get(`http://laotie.com/getclass`)
  const score = yield ajax.get(`http://laotie.com/getscore?name=${class[0].name})
}

// 获得这个函数的遍历器
const gen = step()
// 启动遍历器
gen.next() 

写着挺累的,但是async可以快速的简化它。因为await接受的就是一个Prmoise函数,所以我们可以直接在await后面使用axios,然后直接使用对象解构来获取相应的值。

async function step(){
  const {data:{class}} = await axios.get(`http://laotie.com/getclass`)
  const {data:{core}} = await axios.get(`http://laotie.com/getscore?name=${class[0].name})
    return {class,core}
}

这样是不是就方便多啦!